Skip to content

Commit

Permalink
Add tooling to truncate process history
Browse files Browse the repository at this point in the history
+ Automate test of this functionality.
+ Expand web host admin interface with a path to truncate the process history and delete obsolete files.
+ Also, simplify locking in web host admin interface.
  • Loading branch information
Viir committed May 9, 2020
1 parent 6c8410e commit 5af44c0
Show file tree
Hide file tree
Showing 7 changed files with 353 additions and 91 deletions.
77 changes: 77 additions & 0 deletions implement/PersistentProcess/PersistentProcess.Test/TestWebHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1111,6 +1111,83 @@ public void tooling_supports_replicate_process_from_remote_host()
}
}

[TestMethod]
public void Web_host_supports_truncate_process_history()
{
var allEventsAndExpectedResponses =
TestSetup.CounterProcessTestEventsAndExpectedResponses(
new (int addition, int expectedResponse)[]
{
(0, 0),
(1, 1),
(3, 4),
(5, 9),
(7, 16),
(11, 27),
(-13, 14),
}).ToList();

var eventsAndExpectedResponsesBatches = allEventsAndExpectedResponses.Batch(3).ToList();

var firstBatchOfCounterAppEvents = eventsAndExpectedResponsesBatches.First();

var secondBatchOfCounterAppEvents = eventsAndExpectedResponsesBatches.Skip(1).First();

using (var testSetup = WebHostAdminInterfaceTestSetup.Setup(
deployAppConfigAndInitElmState: TestElmWebAppHttpServer.CounterWebApp))
{
int countFilesInProcessFileStore() =>
testSetup.BuildProcessStoreFileStoreReaderInFileDirectory()
.ListFilesInDirectory(ImmutableList<string>.Empty).Count();

using (var server = testSetup.StartWebHost())
{
using (var publicAppClient = testSetup.BuildPublicAppHttpClient())
{
foreach (var (serializedEvent, expectedResponse) in firstBatchOfCounterAppEvents)
{
var httpResponse =
publicAppClient.PostAsync("", new StringContent(serializedEvent, System.Text.Encoding.UTF8)).Result;

var httpResponseContent = httpResponse.Content.ReadAsStringAsync().Result;

Assert.AreEqual(expectedResponse, httpResponseContent, false, "server response");
}

var numberOfFilesBefore = countFilesInProcessFileStore();

using (var adminClient = testSetup.SetDefaultRequestHeaderAuthorizeForAdminRoot(
testSetup.BuildAdminInterfaceHttpClient()))
{
var truncateResponse = adminClient.PostAsync(
StartupAdminInterface.PathApiTruncateProcessHistory, null).Result;

Assert.IsTrue(
truncateResponse.IsSuccessStatusCode,
"truncateResponse IsSuccessStatusCode (" +
truncateResponse.Content.ReadAsStringAsync().Result + ")");
}

var numberOfFilesAfter = countFilesInProcessFileStore();

Assert.IsTrue(
numberOfFilesAfter < numberOfFilesBefore,
"Number of files in store is lower after truncate request.");

foreach (var (serializedEvent, expectedResponse) in secondBatchOfCounterAppEvents)
{
var httpResponse =
publicAppClient.PostAsync("", new StringContent(serializedEvent, System.Text.Encoding.UTF8)).Result;

var httpResponseContent = httpResponse.Content.ReadAsStringAsync().Result;

Assert.AreEqual(expectedResponse, httpResponseContent, false, "server response");
}
}
}
}
}


class FileStoreFromDelegates : IFileStore
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,9 +175,12 @@ public void Dispose()
}
}

public Kalmit.IFileStoreReader BuildProcessStoreFileStoreReaderInFileDirectory() =>
new FileStoreFromSystemIOFile(ProcessStoreDirectory);

public WebHost.ProcessStoreSupportingMigrations.ProcessStoreReaderInFileStore BuildProcessStoreReaderInFileDirectory() =>
new WebHost.ProcessStoreSupportingMigrations.ProcessStoreReaderInFileStore(
new FileStoreFromSystemIOFile(ProcessStoreDirectory));
BuildProcessStoreFileStoreReaderInFileDirectory());

public IEnumerable<PersistentProcess.InterfaceToHost.Event> EnumerateStoredUpdateElmAppStateForEvents()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
Expand Down Expand Up @@ -77,28 +78,42 @@ static Composition.TreeComponent SubtreeElmAppFromAppConfig(Composition.TreeComp
this.lastSetElmAppStateResult = lastSetElmAppStateResult;
}

static public PersistentProcessVolatileRepresentation Restore(
IProcessStoreReader storeReader,
Action<string> logger,
ElmAppInterfaceConfig? overrideElmAppInterfaceConfig = null)
static public IImmutableDictionary<IImmutableList<string>, IImmutableList<byte>> GetFilesForRestoreProcess(
IFileStoreReader fileStoreReader)
{
var restoreStopwatch = System.Diagnostics.Stopwatch.StartNew();

logger?.Invoke("Begin to restore the process state.");
var filesForProcessRestore = new ConcurrentDictionary<IImmutableList<string>, IImmutableList<byte>>();

if (!storeReader.EnumerateSerializedCompositionLogRecordsReverse().Take(1).Any())
var recordingReader = new Kalmit.DelegatingFileStoreReader
{
logger?.Invoke("Found no composition record, default to initial state.");
GetFileContentDelegate = filePath =>
{
var fileContent = fileStoreReader.GetFileContent(filePath);

return new PersistentProcessVolatileRepresentation(
lastCompositionLogRecordHashBase16: CompositionLogRecordInFile.compositionLogFirstRecordParentHashBase16,
lastAppConfig: null,
lastElmAppVolatileProcess: null,
lastSetElmAppStateResult: null);
if (fileContent != null)
{
filesForProcessRestore[filePath] = fileContent.ToImmutableList();
}

return fileContent;
}
};

/*
Following part can be made less expensive when we have an implementation that evaluates all readings but skips the
rest of the restoring. Something like an extension of `EnumerateCompositionLogRecordsForRestoreProcess` resolving all
dependencies on the store.
*/
using (var restoredProcess = Restore(new ProcessStoreReaderInFileStore(recordingReader), _ => { }))
{
}

var compositionEventsToLatestReductionReversed =
storeReader.EnumerateSerializedCompositionLogRecordsReverse()
return filesForProcessRestore.ToImmutableDictionary();
}

static IEnumerable<(CompositionLogRecordInFile compositionRecord, string compositionRecordHashBase16, LoadedReduction reduction)>
EnumerateCompositionLogRecordsForRestoreProcess(IProcessStoreReader storeReader) =>
storeReader
.EnumerateSerializedCompositionLogRecordsReverse()
.Select(serializedCompositionLogRecord =>
{
var compositionRecordHashBase16 =
Expand Down Expand Up @@ -145,7 +160,30 @@ static public PersistentProcessVolatileRepresentation Restore(
compositionRecordHashBase16: compositionRecordHashBase16,
reduction: loadedReduction);
})
.TakeUntil(compositionAndReduction => compositionAndReduction.reduction != null)
.TakeUntil(compositionAndReduction => compositionAndReduction.reduction != null);

static public PersistentProcessVolatileRepresentation Restore(
IProcessStoreReader storeReader,
Action<string> logger,
ElmAppInterfaceConfig? overrideElmAppInterfaceConfig = null)
{
var restoreStopwatch = System.Diagnostics.Stopwatch.StartNew();

logger?.Invoke("Begin to restore the process state.");

if (!storeReader.EnumerateSerializedCompositionLogRecordsReverse().Take(1).Any())
{
logger?.Invoke("Found no composition record, default to initial state.");

return new PersistentProcessVolatileRepresentation(
lastCompositionLogRecordHashBase16: CompositionLogRecordInFile.compositionLogFirstRecordParentHashBase16,
lastAppConfig: null,
lastElmAppVolatileProcess: null,
lastSetElmAppStateResult: null);
}

var compositionEventsToLatestReductionReversed =
EnumerateCompositionLogRecordsForRestoreProcess(storeReader)
.ToImmutableList();

logger?.Invoke("Found " + compositionEventsToLatestReductionReversed.Count + " composition log records to use for restore.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ namespace Kalmit.PersistentProcess.WebHost
{
public class Program
{
static public string AppVersionId => "2020-05-08";
static public string AppVersionId => "2020-05-09";
}
}
Loading

0 comments on commit 5af44c0

Please sign in to comment.