255 lines
7.4 KiB
C#
255 lines
7.4 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Data.Common;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using log4net;
|
|
using LeafWeb.Core.Utility;
|
|
using Polly;
|
|
using Renci.SshNet;
|
|
using Renci.SshNet.Common;
|
|
|
|
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);
|
|
|
|
private readonly int _connectionRetryCount = 3;
|
|
private readonly Policy _connectRetryPolicy;
|
|
|
|
public PiscalSshClient(string connectionString)
|
|
{
|
|
if (string.IsNullOrEmpty(connectionString))
|
|
throw new ArgumentNullException("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);
|
|
|
|
_connectRetryPolicy = Policy
|
|
.Handle<SshConnectionException>()
|
|
.Retry(_connectionRetryCount,
|
|
(ex, i) => _logger.Warn($"Retry {i} after exception: {ex.Message}"));
|
|
}
|
|
|
|
private SshClient GetSshClient()
|
|
{
|
|
return new SshClient(_connectionInfo);
|
|
}
|
|
|
|
private ScpClient GetScpClient()
|
|
{
|
|
return new ScpClient(_connectionInfo);
|
|
}
|
|
|
|
private void Connect(PiscalLeafInput leafInput, BaseClient client)
|
|
{
|
|
try
|
|
{
|
|
_connectRetryPolicy.Execute(client.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
|
|
{
|
|
_connectRetryPolicy.Execute(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)
|
|
{
|
|
// create directory
|
|
using (var sshClient = GetSshClient())
|
|
{
|
|
Connect(leafInput, sshClient);
|
|
sshClient.RunCommand($"mkdir {BaseDirectory}/{leafInput.PiscalDirectoryName}");
|
|
sshClient.RunCommand($"mkdir {BaseDirectory}/{leafInput.PiscalDirectoryName}/input");
|
|
Disconnect(leafInput, sshClient);
|
|
}
|
|
// copy files
|
|
using (var scp = GetScpClient())
|
|
foreach (var file in leafInput.InputFiles)
|
|
{
|
|
var inputPath = $"{BaseDirectory}/{leafInput.PiscalDirectoryName}/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");
|
|
|
|
CopyLeafInput(leafInput);
|
|
|
|
// 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();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the leaf output from piscal, only run on if result status is success
|
|
/// </summary>
|
|
public IEnumerable<PiscalLeafOutputFile> RetrieveLeafOutput(PiscalLeafInput leafInput)
|
|
{
|
|
// get output files
|
|
var status = GetLeafInputStatusRaw(leafInput);
|
|
if (status[0] != StatusComplete)
|
|
throw new PiscalClientException(leafInput.LeafInputId, "Output not available, status is not StatusComplete, instead is currently " + 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);
|
|
}
|
|
}
|
|
}
|
|
} |