Harden installer update activation fallback
This commit is contained in:
@@ -41,6 +41,8 @@ namespace BizTalkPlatformManagementTool.Tests
|
||||
Run("InstallerActivatesValidatedPayload", InstallerActivatesValidatedPayload);
|
||||
Run("InstallerRetriesTransientActivationMove", InstallerRetriesTransientActivationMove);
|
||||
Run("InstallerUsesVerifiedCopyFallbackForNewInstall", InstallerUsesVerifiedCopyFallbackForNewInstall);
|
||||
Run("InstallerUsesVerifiedCopyFallbackForBackedUpUpdate", InstallerUsesVerifiedCopyFallbackForBackedUpUpdate);
|
||||
Run("InstallerRollsBackFailedUpdateCopyFallback", InstallerRollsBackFailedUpdateCopyFallback);
|
||||
Run("InstallerStopsAfterBoundedUpdateMoveRetries", InstallerStopsAfterBoundedUpdateMoveRetries);
|
||||
Run("InstallerDoesNotMutateOnStagingFailure", InstallerDoesNotMutateOnStagingFailure);
|
||||
Run("InstallerRollsBackFailedActivatedSelfTest", InstallerRollsBackFailedActivatedSelfTest);
|
||||
@@ -379,10 +381,88 @@ namespace BizTalkPlatformManagementTool.Tests
|
||||
Assert(log.Contains("event=directory_move_retry role=activate failed_attempt=7"), "final scheduled retry was not diagnosed");
|
||||
Assert(log.Contains("exception_type=System.UnauthorizedAccessException"), "ACL failure type missing from retry diagnostics");
|
||||
Assert(log.Contains("event=activation_fallback_complete method=verified_copy"), "copy fallback completion was not diagnosed");
|
||||
Assert(log.Contains("scope=new_install"), "new-install fallback scope missing from diagnostics");
|
||||
Assert(log.Contains("activation_method=verified_copy_fallback"), "copy fallback missing from setup summary");
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>Prüft den verifizierten Kopierfallback, nachdem eine Update-Vorversion atomar gesichert wurde.</summary>
|
||||
private static void InstallerUsesVerifiedCopyFallbackForBackedUpUpdate()
|
||||
{
|
||||
InTemp(directory =>
|
||||
{
|
||||
var package = CreatePackage(directory, "new");
|
||||
var install = Path.Combine(directory, "install");
|
||||
Directory.CreateDirectory(install);
|
||||
File.WriteAllText(Path.Combine(install, InstallerEngine.ApplicationExeName), "old");
|
||||
File.WriteAllText(Path.Combine(install, InstallerEngine.ApplicationExeName + ".config"), "old-config");
|
||||
var data = Path.Combine(directory, "data");
|
||||
var activationMoveAttempts = 0;
|
||||
var delays = new List<int>();
|
||||
var engine = new InstallerEngine(
|
||||
package,
|
||||
install,
|
||||
data,
|
||||
false,
|
||||
path => true,
|
||||
(source, target) =>
|
||||
{
|
||||
if (source.IndexOf(".staging.", StringComparison.Ordinal) >= 0)
|
||||
{
|
||||
activationMoveAttempts++;
|
||||
throw new UnauthorizedAccessException("simulated staging rename policy denial");
|
||||
}
|
||||
Directory.Move(source, target);
|
||||
},
|
||||
delays.Add);
|
||||
|
||||
engine.Install(false, null);
|
||||
|
||||
Assert(activationMoveAttempts == 8, "update activation did not exhaust the bounded move attempts");
|
||||
Assert(delays.SequenceEqual(new[] { 250, 500, 1000, 2000, 3000, 5000, 8000 }), "unexpected update fallback retry schedule");
|
||||
Assert(File.ReadAllText(Path.Combine(install, InstallerEngine.ApplicationExeName)) == "new", "update copy fallback did not activate the new payload");
|
||||
Assert(!Directory.GetDirectories(directory, "install.backup.*").Any(), "successful update fallback left a backup directory");
|
||||
var log = File.ReadAllText(Directory.GetFiles(Path.Combine(data, "InstallerLogs"), "setup-*.log").Single());
|
||||
Assert(log.Contains("event=activation_fallback_complete method=verified_copy scope=update_after_backup"), "update fallback scope missing from diagnostics");
|
||||
Assert(log.Contains("activation_method=verified_copy_fallback"), "update copy fallback missing from setup summary");
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>Prüft das Backup-Rollback, wenn der Ziel-Self-Test nach einem Update-Kopierfallback fehlschlägt.</summary>
|
||||
private static void InstallerRollsBackFailedUpdateCopyFallback()
|
||||
{
|
||||
InTemp(directory =>
|
||||
{
|
||||
var package = CreatePackage(directory, "new");
|
||||
var install = Path.Combine(directory, "install");
|
||||
Directory.CreateDirectory(install);
|
||||
File.WriteAllText(Path.Combine(install, InstallerEngine.ApplicationExeName), "old");
|
||||
File.WriteAllText(Path.Combine(install, InstallerEngine.ApplicationExeName + ".config"), "old-config");
|
||||
var selfTestCalls = 0;
|
||||
var engine = new InstallerEngine(
|
||||
package,
|
||||
install,
|
||||
Path.Combine(directory, "data"),
|
||||
false,
|
||||
path => ++selfTestCalls == 1,
|
||||
(source, target) =>
|
||||
{
|
||||
if (source.IndexOf(".staging.", StringComparison.Ordinal) >= 0)
|
||||
throw new UnauthorizedAccessException("simulated staging rename policy denial");
|
||||
Directory.Move(source, target);
|
||||
},
|
||||
milliseconds => { });
|
||||
|
||||
var exception = Capture<InvalidOperationException>(() => engine.Install(false, null));
|
||||
|
||||
Assert(File.ReadAllText(Path.Combine(install, InstallerEngine.ApplicationExeName)) == "old", "failed update fallback did not restore the previous payload");
|
||||
Assert(!Directory.GetDirectories(directory, "install.backup.*").Any(), "rollback left the previous version in a backup directory");
|
||||
Assert(!Directory.GetDirectories(directory, "install.staging.*").Any(), "rollback left the failed staging directory");
|
||||
Assert(exception.Message.Contains("Fehlercode=SETUP-ACTIVATED-SELFTEST"), "failed fallback reported the wrong setup phase");
|
||||
Assert(exception.Message.Contains("Rollback erfolgreich"), "failed fallback did not report successful rollback");
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>Prüft, dass ein Update bei dauerhaft gesperrtem Backup atomar und unverändert abbricht.</summary>
|
||||
private static void InstallerStopsAfterBoundedUpdateMoveRetries()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user