Das Update-Angebot gab es nur im Desktop-Fenster. Der Manager prueft jetzt selbst und spielt ein: erst alle Instanzen geordnet anhalten, dann - ohne systemd: update-agent mit --wait-for-pid und --restart, - unter systemd: clawddotnet-update.service anstossen (Vorlage unter deploy/systemd), damit der agent keinen zweiten Manager neben dem Dienst startet (DC SETUP_INTEGRATION_GUIDE 4A). Fehlt die Unit, wird nichts angehalten; scheitert die Uebergabe, kommen die Instanzen wieder hoch. Anleitung in InstanceSetupGuide Abschnitt 8. Nebenbei: Sprachpaket wird vor dem Kopf geladen (rohe Schluessel im Kopf). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
155 lines
6.1 KiB
C#
155 lines
6.1 KiB
C#
using System.Text.Json;
|
|
using ClawdDotNet.Core.Config;
|
|
using ClawdDotNet.Core.Instances;
|
|
using Deploymentcenter.Client;
|
|
using Deploymentcenter.Client.Models;
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
using Shouldly;
|
|
|
|
namespace ClawdDotNet.Manager.Tests;
|
|
|
|
/// <summary>
|
|
/// #795: Updates from the web interface. The promise: instances are stopped before anything is
|
|
/// handed over, nothing is stopped for nothing, and a failed hand-over brings them back.
|
|
/// </summary>
|
|
public sealed class ManagerUpdatesTests : IAsyncDisposable
|
|
{
|
|
private readonly string _root = Path.Combine(
|
|
Path.GetTempPath(), "clawd-tests", "mupd-" + Guid.NewGuid().ToString("N")[..8]);
|
|
|
|
private readonly FakeLauncher _launcher = new();
|
|
private readonly FakeChannel _channel = new();
|
|
private readonly InstanceDirectoryManager _directories;
|
|
private readonly List<string[]> _systemctlCalls = [];
|
|
|
|
private static readonly SupervisionOptions Options = new(
|
|
ProbeInterval: TimeSpan.FromMilliseconds(50),
|
|
StartTimeout: TimeSpan.FromMilliseconds(600),
|
|
SilentChecksBeforeAlarm: 3,
|
|
StopGrace: TimeSpan.FromMilliseconds(300),
|
|
SilentKillGrace: TimeSpan.FromMilliseconds(300),
|
|
MaxDrainWait: TimeSpan.FromSeconds(2));
|
|
|
|
public ManagerUpdatesTests()
|
|
{
|
|
Directory.CreateDirectory(_root);
|
|
_directories = new InstanceDirectoryManager(Path.Combine(_root, "Instances"));
|
|
_channel.Launcher = _launcher;
|
|
}
|
|
|
|
public async ValueTask DisposeAsync()
|
|
{
|
|
await Task.CompletedTask;
|
|
try { Directory.Delete(_root, recursive: true); }
|
|
catch { /* A leftover test folder does not turn a run red. */ }
|
|
}
|
|
|
|
private async Task<ManagerCore> RunningManagerAsync(string? license = "CLAWD-TEST-KEY")
|
|
{
|
|
var path = _directories.CreateInstance("Alpha");
|
|
var file = Path.Combine(path, "InstanceSettings.json");
|
|
var config = JsonSerializer.Deserialize<InstanceConfig>(File.ReadAllText(file))!;
|
|
config.AutostartByManager = true;
|
|
File.WriteAllText(file, JsonSerializer.Serialize(config));
|
|
_channel.AnswerFor[Path.GetFullPath(path)] = new InstanceIdentity("i-alpha", "Alpha", "0.1.18");
|
|
|
|
var store = new ManagerSettingsStore(_root);
|
|
store.Settings.LicenseKey = license ?? "";
|
|
var registry = new InstanceRegistry(_directories, _launcher, _channel, NullLoggerFactory.Instance, Options);
|
|
var manager = new ManagerCore(store, registry, new FakeTokenSource(), NullLoggerFactory.Instance);
|
|
await manager.StartAsync();
|
|
manager.Instances.Instances.Single().Condition.ShouldBe(InstanceCondition.Running);
|
|
return manager;
|
|
}
|
|
|
|
private ManagerUpdates Updates(ManagerCore manager, bool systemd, bool unitInstalled = true,
|
|
string? agentProblem = null, int startExit = 0)
|
|
=> new(manager, "0.1.18", NullLogger.Instance,
|
|
check: _ => Task.FromResult(new UpdateCheckResult { UpdateAvailable = true, LatestRelease = new VersionInfo { Version = "0.1.19" } }),
|
|
launchAgent: () => agentProblem,
|
|
systemctl: args =>
|
|
{
|
|
_systemctlCalls.Add(args);
|
|
return args.Contains("cat") ? (unitInstalled ? 0 : 1, "") : (startExit, startExit == 0 ? "" : "failed");
|
|
},
|
|
underSystemd: systemd);
|
|
|
|
[Fact]
|
|
public async Task Without_systemd_the_instances_stop_and_the_manager_ends()
|
|
{
|
|
await using var manager = await RunningManagerAsync();
|
|
var updates = Updates(manager, systemd: false);
|
|
var ended = false;
|
|
updates.ShutdownRequested += () => ended = true;
|
|
|
|
var result = await updates.InstallAsync();
|
|
|
|
result.Ok.ShouldBeTrue(result.Message);
|
|
ended.ShouldBeTrue();
|
|
manager.Instances.Instances.Single().Condition.ShouldBe(InstanceCondition.Stopped);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Under_systemd_the_update_unit_is_started_without_blocking()
|
|
{
|
|
await using var manager = await RunningManagerAsync();
|
|
var updates = Updates(manager, systemd: true);
|
|
var ended = false;
|
|
updates.ShutdownRequested += () => ended = true;
|
|
|
|
(await updates.InstallAsync()).Ok.ShouldBeTrue();
|
|
|
|
_systemctlCalls.ShouldContain(a => a.SequenceEqual(new[] { "--user", "start", "--no-block", ManagerUpdates.UpdateUnit }));
|
|
ended.ShouldBeFalse(); // the unit stops the service; the manager does not end itself
|
|
manager.Instances.Instances.Single().Condition.ShouldBe(InstanceCondition.Stopped);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Without_the_update_unit_nothing_is_stopped()
|
|
{
|
|
await using var manager = await RunningManagerAsync();
|
|
|
|
var result = await Updates(manager, systemd: true, unitInstalled: false).InstallAsync();
|
|
|
|
result.Ok.ShouldBeFalse();
|
|
result.Message.ShouldContain(ManagerUpdates.UpdateUnit);
|
|
manager.Instances.Instances.Single().Condition.ShouldBe(InstanceCondition.Running);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task A_failed_hand_over_brings_the_instances_back()
|
|
{
|
|
await using var manager = await RunningManagerAsync();
|
|
var updates = Updates(manager, systemd: false, agentProblem: "agent not found");
|
|
var ended = false;
|
|
updates.ShutdownRequested += () => ended = true;
|
|
|
|
var result = await updates.InstallAsync();
|
|
|
|
result.Ok.ShouldBeFalse();
|
|
ended.ShouldBeFalse();
|
|
manager.Instances.Instances.Single().Condition.ShouldBe(InstanceCondition.Running);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Without_a_license_key_nothing_happens()
|
|
{
|
|
await using var manager = await RunningManagerAsync(license: null);
|
|
|
|
(await Updates(manager, systemd: false).InstallAsync()).Ok.ShouldBeFalse();
|
|
manager.Instances.Instances.Single().Condition.ShouldBe(InstanceCondition.Running);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task The_check_reports_the_new_version()
|
|
{
|
|
await using var manager = await RunningManagerAsync();
|
|
|
|
var status = await Updates(manager, systemd: false).CheckAsync();
|
|
|
|
status.Available.ShouldBeTrue();
|
|
status.LatestVersion.ShouldBe("0.1.19");
|
|
status.InstalledVersion.ShouldBe("0.1.18");
|
|
}
|
|
}
|