using System; using System.Collections.Generic; using System.Data.Common; using System.IO; using System.Linq; using LeafWeb.Core.Utility; using NLog; 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 PasswordConnectionInfo _connectionInfo; private const string StatusComplete = "complete"; private const string StatusRunning = "running"; private const string StatusNotStarted = "not started"; Logger _logger = LogManager.GetCurrentClassLogger(); public PiscalSshClient(string connectionString) { var conn = new DbConnectionStringBuilder {ConnectionString = connectionString}; var 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 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.Trace("Copying " + inputPath); scp.Connect(); scp.Upload(stream, inputPath); scp.Disconnect(); } } } public void RunLeafInput(PiscalLeafInput leafInput) { var inputDirectory = $"{BaseDirectory}/{leafInput.DirectoryName}"; CopyLeafInput(leafInput, inputDirectory); // begin processing using (var ssh = GetSshClient()) { ssh.Connect(); var commandText = $"{RemoteScriptPath} -d {leafInput.DirectoryName} -p {leafInput.PhotosyntheticType} -s"; var command = ssh.CreateCommand(commandText); command.Execute(); ssh.Disconnect(); if (command.ExitStatus != 0) throw new PiscalClientException(leafInput.LeafInputId, command.Result); _logger.Debug("RunLeafInput result: " + command.Result); } } 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()) { ssh.Connect(); var commandText = $"{RemoteScriptPath} -d {leafInput.DirectoryName}"; var command = ssh.CreateCommand(commandText); command.Execute(); ssh.Disconnect(); if (command.ExitStatus != 0) throw new PiscalClientException(leafInput.LeafInputId, command.Result); 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()) { scp.Connect(); foreach (var filePath in filePaths) { using (var stream = new MemoryStream()) { scp.Download(filePath, stream); yield return new PiscalLeafOutputFile { Contents = stream.ToArray(), Filename = filePath.FilenameFromPath(), DirectoryName = leafInput.DirectoryName }; } } scp.Disconnect(); } } public void CleanupLeafProcess(PiscalLeafInput leafInput) { using (var ssh = GetSshClient()) { ssh.Connect(); var commandText = $"{RemoteScriptPath} -d {leafInput.DirectoryName} -c"; var command = ssh.CreateCommand(commandText); command.Execute(); ssh.Disconnect(); if (command.ExitStatus != 0) throw new PiscalClientException(leafInput.LeafInputId, command.Error); } } } }