using System; using System.Collections.Generic; using System.Data.Common; using System.IO; using System.Linq; using log4net; using LeafWeb.Core.Utility; using Renci.SshNet; namespace LeafWeb.Core.Remote { public class PiscalSshClient : IPiscalClient { private const string BaseDirectory = "./LeafWeb"; private const string RemoteScriptPath = BaseDirectory + "/piscal_manager.sh"; private readonly string _host; private readonly PasswordConnectionInfo _connectionInfo; private const string StatusComplete = "complete"; private const string StatusRunning = "running"; private const string StatusNotStarted = "not started"; private readonly ILog _logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); public PiscalSshClient(string connectionString) { var conn = new DbConnectionStringBuilder {ConnectionString = connectionString}; _host = conn["host"] as string; var username = conn["username"] as string; var password = conn["password"] as string; _connectionInfo = new PasswordConnectionInfo(_host, username, password); } private SshClient GetSshClient() { return new SshClient(_connectionInfo); } private ScpClient GetScpClient() { return new ScpClient(_connectionInfo); } private void Connect(PiscalLeafInput leafInput, BaseClient scp) { try { scp.Connect(); } catch (Exception ex) { var message = $"Exception while connecting to client. Message: {ex.Message}"; _logger.Error(message); throw new PiscalClientException(leafInput.LeafInputId, message); } } private void Disconnect(PiscalLeafInput leafInput, BaseClient client) { try { client.Disconnect(); } catch (Exception ex) { var message = $"Exception while disconnecting from client. Message: {ex.Message}"; _logger.Error(message); throw new PiscalClientException(leafInput.LeafInputId, message); } } private void CopyLeafInput(PiscalLeafInput leafInput, string directory) { // copy files using (var scp = GetScpClient()) foreach (var file in leafInput.InputFiles) { var inputPath = $"{directory}/input/{file.Filename}"; using (var stream = new MemoryStream(file.Contents)) { _logger.Debug("Copying " + inputPath); Connect(leafInput, scp); scp.Upload(stream, inputPath); Disconnect(leafInput, scp); } } } public string Host => _host; public void RunLeafInput(PiscalLeafInput leafInput) { if (string.IsNullOrEmpty(leafInput.PhotosyntheticType)) throw new PiscalClientException(leafInput.LeafInputId, "No PhotosyntheticType set"); var inputDirectory = $"{BaseDirectory}/{leafInput.PiscalDirectoryName}"; CopyLeafInput(leafInput, inputDirectory); // begin processing using (var ssh = GetSshClient()) { Connect(leafInput, ssh); var commandText = $"{RemoteScriptPath} -d {leafInput.PiscalDirectoryName} -p {leafInput.PhotosyntheticType} -s"; if (leafInput.SuppressStorageCopy) commandText += " -t"; if (!string.IsNullOrEmpty(leafInput.NotifyCompleteUrl)) commandText += $" -u {leafInput.NotifyCompleteUrl}"; var command = ssh.CreateCommand(commandText); command.Execute(); Disconnect(leafInput, ssh); if (command.ExitStatus != 0) throw new PiscalClientException(leafInput.LeafInputId, command); var result = command.Result.TrimEndNewLine(); if (result == "started") _logger.Info($"RunLeafInput result: {result}"); else _logger.Warn($"RunLeafInput result: {result}, commandText: {commandText}"); } } public PiscalStatus GetLeafInputStatus(PiscalLeafInput leafInput) { var statusRaw = GetLeafInputStatusRaw(leafInput); switch (statusRaw[0]) { case StatusRunning: return PiscalStatus.Running; case StatusComplete: return PiscalStatus.Complete; case StatusNotStarted: return PiscalStatus.NotStarted; default: throw new PiscalClientException(leafInput.LeafInputId, "Unknown status: " + statusRaw[0]); } } private string[] GetLeafInputStatusRaw(PiscalLeafInput leafInput) { using (var ssh = GetSshClient()) { Connect(leafInput, ssh); var commandText = $"{RemoteScriptPath} -d {leafInput.PiscalDirectoryName}"; var command = ssh.CreateCommand(commandText); command.Execute(); Disconnect(leafInput, ssh); if (command.ExitStatus != 0) throw new PiscalClientException(leafInput.LeafInputId, command); return command.Result .SplitNewLine() .Where(s => s.Length > 0) .Select(s => s.Trim()).ToArray(); } } /// /// Gets the leaf output from piscal, only run on if result status is success /// public IEnumerable RetrieveLeafOutput(PiscalLeafInput leafInput) { // get output files var status = GetLeafInputStatusRaw(leafInput); if (status[0] != StatusComplete) throw new PiscalClientException(leafInput.LeafInputId, "output not available, status is " + status[0]); var filePaths = status.Skip(1); using (var scp = GetScpClient()) { Connect(leafInput, scp); string outputFileType = string.Empty; foreach (var filePath in filePaths) { if (filePath.StartsWith("#")) { outputFileType = filePath.Substring(1); continue; } using (var stream = new MemoryStream()) { scp.Download(filePath, stream); yield return new PiscalLeafOutputFile { Contents = stream.ToArray(), Filename = filePath.FilenameFromPath(), PiscalDirectoryName = leafInput.PiscalDirectoryName, OutputFileType = outputFileType }; } } Disconnect(leafInput, scp); } } public void KillLeafProcess(PiscalLeafInput leafInput) { using (var ssh = GetSshClient()) { Connect(leafInput, ssh); var commandText = $"{RemoteScriptPath} -d {leafInput.PiscalDirectoryName} -k"; var command = ssh.CreateCommand(commandText); command.Execute(); Disconnect(leafInput, ssh); if (command.ExitStatus != 0) throw new PiscalClientException(leafInput.LeafInputId, command); } } public void CleanupLeafProcess(PiscalLeafInput leafInput) { using (var ssh = GetSshClient()) { Connect(leafInput, ssh); var commandText = $"{RemoteScriptPath} -d {leafInput.PiscalDirectoryName} -c"; var command = ssh.CreateCommand(commandText); command.Execute(); Disconnect(leafInput, ssh); if (command.ExitStatus != 0) throw new PiscalClientException(leafInput.LeafInputId, command); } } } }