Display results from Piscal processing

Error handling for Piscal processing
This commit is contained in:
2016-03-07 11:47:55 -05:00
parent c80d048c39
commit 05b2bfefb1
11 changed files with 191 additions and 58 deletions
+1
View File
@@ -8,5 +8,6 @@ namespace LeafWeb.Core.Remote
PiscalStatus GetLeafInputFileStatus(PiscalLeafInputFile file); PiscalStatus GetLeafInputFileStatus(PiscalLeafInputFile file);
IEnumerable<PiscalLeafOutputFile> RetrieveLeafOutput(PiscalLeafInputFile file); IEnumerable<PiscalLeafOutputFile> RetrieveLeafOutput(PiscalLeafInputFile file);
void CleanupLeafProcess(PiscalLeafInputFile file); void CleanupLeafProcess(PiscalLeafInputFile file);
string GetErrorMessage(PiscalLeafInputFile file);
} }
} }
+15 -2
View File
@@ -16,6 +16,7 @@ namespace LeafWeb.Core.Remote
private const string StatusSuccess = "success"; private const string StatusSuccess = "success";
private const string StatusRunning = "running"; private const string StatusRunning = "running";
private const string StatusNotStarted = "not started";
private const string StatusError = "error"; private const string StatusError = "error";
public PiscalSshClient(string connectionString) public PiscalSshClient(string connectionString)
@@ -62,7 +63,7 @@ namespace LeafWeb.Core.Remote
ssh.Disconnect(); ssh.Disconnect();
if (command.ExitStatus != 0) if (command.ExitStatus != 0)
throw new PiscalClientException(command.Error); throw new PiscalClientException(command.Result);
Console.Write(command.Result); Console.Write(command.Result);
} }
@@ -78,6 +79,8 @@ namespace LeafWeb.Core.Remote
return PiscalStatus.Running; return PiscalStatus.Running;
case StatusSuccess: case StatusSuccess:
return PiscalStatus.Success; return PiscalStatus.Success;
case StatusNotStarted:
return PiscalStatus.NotStarted;
default: default:
return PiscalStatus.Error; return PiscalStatus.Error;
} }
@@ -94,7 +97,7 @@ namespace LeafWeb.Core.Remote
ssh.Disconnect(); ssh.Disconnect();
if (command.ExitStatus != 0) if (command.ExitStatus != 0)
throw new PiscalClientException(command.Error); throw new PiscalClientException(command.Result);
return command.Result return command.Result
.SplitNewLine() .SplitNewLine()
@@ -137,6 +140,16 @@ namespace LeafWeb.Core.Remote
} }
} }
public string GetErrorMessage(PiscalLeafInputFile file)
{
var status = GetLeafInputStatusRaw(file);
if (status[0] != StatusError)
return string.Empty;
var errorLines = status.Skip(1).ToArray();
return errorLines.Join(Environment.NewLine);
}
public void CleanupLeafProcess(PiscalLeafInputFile file) public void CleanupLeafProcess(PiscalLeafInputFile file)
{ {
var status = GetLeafInputStatusRaw(file); var status = GetLeafInputStatusRaw(file);
+1
View File
@@ -4,6 +4,7 @@ namespace LeafWeb.Core.Remote
{ {
Running, Running,
Success, Success,
NotStarted,
Error Error
} }
} }
+6
View File
@@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
@@ -50,5 +51,10 @@ namespace LeafWeb.Core.Utility
{ {
return Regex.Replace(path, @".*/([^/]*$)", "$1"); return Regex.Replace(path, @".*/([^/]*$)", "$1");
} }
public static string Join<T>(this IEnumerable<T> enumerable, string separator)
{
return string.Join(separator, enumerable);
}
} }
} }
@@ -1,4 +1,5 @@
using System; using System;
using System.Linq;
using LeafWeb.Core.Entities; using LeafWeb.Core.Entities;
using LeafWeb.Web.ViewModels.LeafOutput; using LeafWeb.Web.ViewModels.LeafOutput;
using NUnit.Framework; using NUnit.Framework;
@@ -8,34 +9,71 @@ namespace LeafWeb.Web.Tests.ViewModels.LeafOutput
[TestFixture] [TestFixture]
public class LeafOutputViewModelTests public class LeafOutputViewModelTests
{ {
private LeafInputFile file = new LeafInputFile private LeafInputFile GetLeafInputFile()
{ {
Filename = "MyFilename.ext", return new LeafInputFile
Id = 3,
CurrentStatus = LeafInputStatusType.Running,
LeafInput = new LeafInput
{ {
Added = DateTime.Today, Filename = "MyFilename.ext",
Email = "test@email.com", Id = 3,
Identifier = "Ident I Fier", CurrentStatus = LeafInputStatusType.Complete,
Name = "My Name", LeafInput = new LeafInput
PhotosynthesisType = new PhotosynthesisType { Id = "1", Name = "1", SortOrder = 1} {
}, Added = DateTime.Today,
LeafOutputFiles = new[] { new LeafOutputFile { Filename = "OutputFilename.txt" } } Email = "test@email.com",
}; Identifier = "Ident I Fier",
Name = "My Name",
PhotosynthesisType = new PhotosynthesisType {Id = "1", Name = "1", SortOrder = 1}
},
LeafOutputFiles = new[] {new LeafOutputFile {Filename = "OutputFilename.txt"}}
};
}
[Test] [Test]
public void CanConstructFromLeafInputFile() public void CanConstructFromLeafInputFile()
{ {
var file = GetLeafInputFile();
var viewModel = new LeafOutputViewModel(file); var viewModel = new LeafOutputViewModel(file);
Assert.That(viewModel.CurrentStatus, Is.EqualTo(file.CurrentStatus.ToString())); Assert.That(viewModel.CurrentStatus, Is.EqualTo(file.CurrentStatus.ToString()));
Assert.That(viewModel.LeafInputFileId, Is.EqualTo(file.Id)); Assert.That(viewModel.LeafInputFileId, Is.EqualTo(file.Id));
Assert.That(viewModel.LeafInputFilename, Is.EqualTo(file.Filename)); Assert.That(viewModel.LeafInputFilename, Is.EqualTo(file.Filename));
Assert.That(viewModel.LeafOutputFilenames, Is.Empty); Assert.That(viewModel.LeafOutputFilenames, Has.Length.EqualTo(1));
Assert.That(viewModel.LeafInputIdentifier, Is.EqualTo(file.LeafInput.Identifier)); Assert.That(viewModel.LeafInputIdentifier, Is.EqualTo(file.LeafInput.Identifier));
Assert.That(viewModel.LeafInputSiteId, Is.EqualTo(file.LeafInput.SiteId)); Assert.That(viewModel.LeafInputSiteId, Is.EqualTo(file.LeafInput.SiteId));
Assert.That(viewModel.LeafInputPhotosynthesisType, Is.EqualTo(file.LeafInput.PhotosynthesisType.Name)); Assert.That(viewModel.LeafInputPhotosynthesisType, Is.EqualTo(file.LeafInput.PhotosynthesisType.Name));
} }
[Test]
public void CanConstructFromLeafInputFile_Running()
{
var file = GetLeafInputFile();
file.CurrentStatus = LeafInputStatusType.Running;
file.LeafOutputFiles = new LeafOutputFile[0];
var viewModel = new LeafOutputViewModel(file);
Assert.That(viewModel.CurrentStatus, Is.EqualTo(LeafInputStatusType.Running.ToString()));
Assert.That(viewModel.LeafOutputFilenames, Has.Length.EqualTo(0));
}
[Test]
public void CanConstructFromLeafInputFile_Error()
{
var file = GetLeafInputFile();
file.CurrentStatus = LeafInputStatusType.Error;
file.StatusHistory = new []
{
new LeafInputFileStatus
{
DateTime = DateTime.Today,
LeafInputFile = file,
Description = "My Error",
Status = LeafInputStatusType.Error
}
};
var viewModel = new LeafOutputViewModel(file);
Assert.That(viewModel.CurrentStatus, Is.EqualTo(LeafInputStatusType.Error.ToString()));
Assert.That(viewModel.ErrorMessages[0], Is.EqualTo(file.StatusHistory.First().Description));
}
} }
} }
+7 -1
View File
@@ -1,4 +1,6 @@
using System.Linq;
using System.Web.Mvc; using System.Web.Mvc;
using LeafWeb.Web.ViewModels.LeafOutput;
namespace LeafWeb.Web.Controllers namespace LeafWeb.Web.Controllers
{ {
@@ -6,7 +8,11 @@ namespace LeafWeb.Web.Controllers
{ {
public ActionResult Index() public ActionResult Index()
{ {
var viewModel = DataService.GetLeafOutputFiles(); var viewModel =
DataService.GetLeafInputFiles()
.OrderByDescending(f => f.Id)
.ToList()
.Select(f => new LeafOutputViewModel(f));
return View(viewModel); return View(viewModel);
} }
} }
+1 -1
View File
@@ -17,7 +17,7 @@
<rules> <rules>
<logger name="Hangfire.*" minlevel="Debug" maxlevel="Info" final="true"/> <logger name="Hangfire.*" minlevel="Debug" maxlevel="Info" final="true"/>
<logger name="*" minlevel="Debug" writeTo="debugLogger"/> <logger name="*" minlevel="Trace" writeTo="debugLogger"/>
<logger name="*" minlevel="Error" writeTo="exceptionLogger" /> <logger name="*" minlevel="Error" writeTo="exceptionLogger" />
</rules> </rules>
</nlog> </nlog>
+68 -30
View File
@@ -31,13 +31,18 @@ namespace LeafWeb.Web.Services
{ {
logger.Trace("ProcessQueue entered"); logger.Trace("ProcessQueue entered");
ProcessRunning(logger); try
{
ProcessRunning(logger);
ProcessQueue(logger); ProcessQueue(logger);
}
finally
{
logger.Trace("ProcessQueue exit");
logger.Trace("ProcessQueue completed"); Monitor.Exit(ProcessQueueLock);
}
Monitor.Exit(ProcessQueueLock);
} }
else else
{ {
@@ -54,14 +59,25 @@ namespace LeafWeb.Web.Services
var queuedFile = var queuedFile =
_dataService _dataService
.GetLeafInputFiles(LeafInputStatusType.Queued) .GetLeafInputFiles(LeafInputStatusType.Queued)
.OrderBy(l => l.Id) .OrderBy(l => l.StatusHistory.Min(sh => sh.DateTime))
.FirstOrDefault(); .FirstOrDefault();
if (queuedFile == null) if (queuedFile == null)
return; return;
logger.Info("LeafInputFile: {0}, Start", queuedFile.Id); logger.Info("LeafInputFile: {0}, Start", queuedFile.Id);
_piscalService.Run(queuedFile); try
{
_piscalService.Run(queuedFile);
}
catch (PiscalClientException ex)
{
logger.Error("LeafInputFile: {0}, ProcessQueue Exception: {1}", queuedFile.Id, ex.Message);
_dataService.SetLeafInputFileStatus(queuedFile, LeafInputStatusType.Error, "Error occurred submitting LeafInput");
// TODO: re-queue
//_dataService.SetLeafInputFileStatus(queuedFile, LeafInputStatusType.Queued, "Re-queuing LeafInput");
}
_dataService.SetLeafInputFileStatus(queuedFile, LeafInputStatusType.Running); _dataService.SetLeafInputFileStatus(queuedFile, LeafInputStatusType.Running);
} }
@@ -71,33 +87,55 @@ namespace LeafWeb.Web.Services
foreach (var file in runningLeafInputFiles) foreach (var file in runningLeafInputFiles)
{ {
var status = _piscalService.GetStatus(file); var status = _piscalService.GetStatus(file);
switch (status) try
{ {
case PiscalStatus.Running: switch (status)
logger.Trace("LeafInputFile: {0}, {1}", file.Id, status); {
// continue running case PiscalStatus.Running:
break; logger.Trace("LeafInputFile: {0}, Running", file.Id);
case PiscalStatus.Success: // continue running
logger.Info("LeafInputFile: {0}, {1}", file.Id, status); break;
// collect the leaf output
var leafOutputFiles = _piscalService.RetrieveOutputFiles(file).ToList();
foreach (var outputFile in leafOutputFiles)
_dataService.AddLeafOutputFile(outputFile);
logger.Info("LeafInputFile: {0}, output files: {1}", file.Id, case PiscalStatus.Success:
string.Join(", ", leafOutputFiles.Select(o => o.Filename))); logger.Info("LeafInputFile: {0}, Success", file.Id);
// collect the leaf output
var leafOutputFiles = _piscalService.RetrieveOutputFiles(file).ToList();
foreach (var outputFile in leafOutputFiles)
_dataService.AddLeafOutputFile(outputFile);
// update db logger.Info("LeafInputFile: {0}, output files: {1}", file.Id,
_dataService.SetLeafInputFileStatus(file, LeafInputStatusType.Complete); string.Join(", ", leafOutputFiles.Select(o => o.Filename)));
// remove working data from the server // update db
logger.Info("LeafInputFile: {0}, cleanup", file.Id); _dataService.SetLeafInputFileStatus(file, LeafInputStatusType.Complete);
_piscalService.Cleanup(file);
break; // remove working data from the server
case PiscalStatus.Error: logger.Info("LeafInputFile: {0}, Cleanup", file.Id);
logger.Info("LeafInputFile: {0}, error", file.Id); _piscalService.Cleanup(file);
_dataService.SetLeafInputFileStatus(file, LeafInputStatusType.Error); break;
break;
case PiscalStatus.NotStarted:
logger.Warn("LeafInputFile: {0}, Not Started, re-queueing", file.Id);
// if it's not started, try to requeue the process - unusual state
_dataService.SetLeafInputFileStatus(file, LeafInputStatusType.Queued);
break;
case PiscalStatus.Error:
logger.Info("LeafInputFile: {0}, Error", file.Id);
var errorMessage = _piscalService.GetErrorMessage(file);
logger.Info("LeafInputFile: {0}, Error Message: {1}", file.Id, errorMessage);
_dataService.SetLeafInputFileStatus(file, LeafInputStatusType.Error, errorMessage);
break;
}
}
catch (PiscalClientException ex)
{
logger.Error("LeafInputFile: {0}, ProcessRunning Exception: {1}", file.Id, ex.Message);
_dataService.SetLeafInputFileStatus(file, LeafInputStatusType.Error, "Error occurred processing LeafInput");
// TODO: re-queue
} }
} }
} }
+6
View File
@@ -42,6 +42,12 @@ namespace LeafWeb.Web.Services
} }
} }
public string GetErrorMessage(LeafInputFile leafInputFile)
{
var inputFile = new PiscalLeafInputFile(leafInputFile);
return _piscalClient.GetErrorMessage(inputFile);
}
public void Cleanup(LeafInputFile leafInputFile) public void Cleanup(LeafInputFile leafInputFile)
{ {
var inputFile = new PiscalLeafInputFile(leafInputFile); var inputFile = new PiscalLeafInputFile(leafInputFile);
@@ -1,5 +1,6 @@
using System.Linq; using System.Linq;
using AutoMapper; using AutoMapper;
using LeafWeb.Core.Entities;
namespace LeafWeb.Web.ViewModels.LeafOutput namespace LeafWeb.Web.ViewModels.LeafOutput
{ {
@@ -10,7 +11,9 @@ namespace LeafWeb.Web.ViewModels.LeafOutput
public int LeafInputFileId { get; set; } public int LeafInputFileId { get; set; }
public string LeafInputFilename { get; set; } public string LeafInputFilename { get; set; }
public string CurrentStatus { get; set; } public string CurrentStatus { get; set; }
public string[] ErrorMessages { get; set; }
public string[] LeafOutputFilenames { get; set; } public string[] LeafOutputFilenames { get; set; }
public string LeafInputName { get; set; }
public string LeafInputIdentifier { get; set; } public string LeafInputIdentifier { get; set; }
public string LeafInputSiteId { get; set; } public string LeafInputSiteId { get; set; }
public string LeafInputPhotosynthesisType { get; set; } public string LeafInputPhotosynthesisType { get; set; }
@@ -20,19 +23,33 @@ namespace LeafWeb.Web.ViewModels.LeafOutput
var config = var config =
new MapperConfiguration(cfg => new MapperConfiguration(cfg =>
{ {
cfg.CreateMap<Core.Entities.LeafInputFile, LeafOutputViewModel>() cfg.CreateMap<LeafInputFile, LeafOutputViewModel>()
.ForMember(dest => dest.LeafInputFileId, opt => opt.MapFrom(src => src.Id)) .ForMember(dest => dest.LeafInputFileId, opt => opt.MapFrom(src => src.Id))
.ForMember(dest => dest.LeafInputFilename, opt => opt.MapFrom(src => src.Filename)) .ForMember(dest => dest.LeafInputFilename, opt => opt.MapFrom(src => src.Filename))
.ForMember(dest => dest.LeafOutputFilenames, .ForMember(dest => dest.LeafOutputFilenames,
opt => opt.ResolveUsing(file => file.LeafOutputFiles?.Select(o => o.Filename).ToArray() ?? new string[] {})) opt => opt.ResolveUsing(
file =>
file.LeafOutputFiles?
.Select(o => o.Filename)
.ToArray()
?? new string[] {}))
.ForMember(dest => dest.LeafInputName, opt => opt.MapFrom(src => src.LeafInput.Name))
.ForMember(dest => dest.LeafInputIdentifier, opt => opt.MapFrom(src => src.LeafInput.Identifier)) .ForMember(dest => dest.LeafInputIdentifier, opt => opt.MapFrom(src => src.LeafInput.Identifier))
.ForMember(dest => dest.LeafInputSiteId, opt => opt.MapFrom(src => src.LeafInput.SiteId)) .ForMember(dest => dest.LeafInputSiteId, opt => opt.MapFrom(src => src.LeafInput.SiteId))
.ForMember(dest => dest.LeafInputPhotosynthesisType, opt => opt.MapFrom(src => src.LeafInput.PhotosynthesisType.Name)); .ForMember(dest => dest.LeafInputPhotosynthesisType, opt => opt.MapFrom(src => src.LeafInput.PhotosynthesisType.Name))
.ForMember(dest => dest.ErrorMessages,
opt => opt.ResolveUsing(
src =>
src.StatusHistory?
.Where(sh => sh.Status == LeafInputStatusType.Error)
.Select(sh => sh.Description)
.ToArray()
?? new string[] {}));
}); });
Mapper = config.CreateMapper(); Mapper = config.CreateMapper();
} }
public LeafOutputViewModel(Core.Entities.LeafInputFile leafInput) public LeafOutputViewModel(LeafInputFile leafInput)
{ {
Mapper.Map(leafInput, this); Mapper.Map(leafInput, this);
} }
+13 -6
View File
@@ -1,12 +1,19 @@
@model LeafWeb.Web.ViewModels.LeafOutput.LeafOutputViewModel @model IEnumerable<LeafWeb.Web.ViewModels.LeafOutput.LeafOutputViewModel>
@{ @{
ViewBag.Title = "Users"; ViewBag.Title = "Results";
var grid = new WebGrid(Model, rowsPerPage: 45); var grid = new WebGrid(Model, rowsPerPage: 45);
} }
<h1>EDO Results</h1> <h1>Results</h1>
<div class="row"> @grid.GetHtml(columns:
grid.Columns(
</div> grid.Column("LeafInputIdentifier", "Identifier"),
grid.Column("LeafInputSiteId", "Site Id"),
grid.Column("LeafInputFilename", "Filename"),
grid.Column("LeafInputName", "Submitted By"),
grid.Column("CurrentStatus", "Status")
),
htmlAttributes: new { @class = "table table-striped table-bordered table-hover table-condensed" }
)