节点在线、应用在线、配置在线使用令牌查询
大石头 authored at 2021-12-16 19:49:30
5.10 KiB
Stardust
using Microsoft.Extensions.DependencyInjection;
using NewLife;
using NewLife.Log;
using NewLife.Remoting;
using NewLife.Remoting.Models;
using NewLife.Security;
using NewLife.Serialization;
using ServerTest.Fixtures;
using Stardust;
using Stardust.Data;
using Stardust.Data.Nodes;
using Stardust.Managers;
using Stardust.Models;
using Stardust.Server.Services;
using Xunit;

namespace ServerTest.Communication;

/// <summary>
/// 节点指令基建验收:真 StarServer + 真 StarClient + ServiceManager.Attach(与 StarAgent 进程相同的注册路径)。
/// 平台「未配置日志目录、不下发指令」由 Stardust.WebApiTests 的 DeploymentDeployLogApiTests 覆盖。
/// </summary>
[Collection("StarServer")]
public class DeployLogNodeCommandTests
{
    private readonly StarServerFixture _fixture;

    public DeployLogNodeCommandTests(StarServerFixture fixture) => _fixture = fixture;

    [Fact(DisplayName = "节点指令-应用日志:list/tail 走真实 DeployLogCommands")]
    public async Task DeployLog_ListAndTail_UsesRealHandler()
    {
        using var seed = new DeployLogSeed();
        var (app, token) = await CreateControllerAppAsync();
        var client = new TestStarClient(_fixture.ServerUrl) { Log = XTrace.Log };
        var manager = new ServiceManager();
        Node? node = null;
        try
        {
            // ClientBase 首次登录超时只有 1 秒,之后按次数递增到 Timeout。
            // 冷启动建节点经常超过 1 秒,必须在同一个客户端上重试,不能每次 new 一个。
            Exception? last = null;
            for (var i = 0; i < 8 && !client.Logined; i++)
            {
                try
                {
                    await client.Login();
                }
                catch (Exception ex)
                {
                    last = ex;
                }
            }
            Assert.True(client.Logined, "节点未能登录:" + last?.Message);
            manager.Attach(client);

            node = Node.FindByCode(client.Code);
            Assert.NotNull(node);
            seed.Bind(node!);
            Assert.Equal(node!.ID, seed.DeployNode!.NodeId);

            var sessions = _fixture.Host.Services.GetRequiredService<NodeSessionManager>();
            for (var i = 0; i < 60 && sessions.Get(node.Code) == null; i++) await Task.Delay(250);
            Assert.NotNull(sessions.Get(node.Code));

            var listArg = new DeployLogListArg { LogDirectory = seed.LogDirectory, Pattern = "*.log" }.ToJson();
            var list = await NodeCommandClient.SendAsync(_fixture.ServerUrl, token, node.Code, "deploy/log/list", listArg!);
            Assert.NotNull(list);
            Assert.Equal(CommandStatus.已完成, list!.Status);
            Assert.Contains(seed.LogFileName, list.Data?.ToString());

            var tailArg = new DeployLogTailArg { LogDirectory = seed.LogDirectory, File = seed.LogFileName, MaxBytes = 65536 }.ToJson();
            var tail = await NodeCommandClient.SendAsync(_fixture.ServerUrl, token, node.Code, "deploy/log/tail", tailArg!);
            Assert.NotNull(tail);
            Assert.Equal(CommandStatus.已完成, tail!.Status);
            Assert.Contains(DeployLogSeed.SampleLine, tail.Data?.ToString());
        }
        finally
        {
            client.Dispose();
            manager.Dispose();
            Cleanup(node);
            CleanupApp(app);
        }
    }

    /// <summary>伪造硬件标识,避免命中本机已注册节点。</summary>
    private sealed class TestStarClient(String urls) : StarClient(urls)
    {
        public override ILoginRequest BuildLoginRequest()
        {
            var request = base.BuildLoginRequest();
            if (request is LoginInfo inf && inf.Node != null)
            {
                inf.Node.Macs = "aabbcc" + Rand.NextBytes(6).ToHex();
                inf.Node.MachineGuid = Guid.NewGuid().ToString("N");
                inf.Node.UUID = Guid.NewGuid().ToString("N");
                inf.Node.SerialNumber = "sn" + Guid.NewGuid().ToString("N");
                inf.Node.DiskID = "disk" + Guid.NewGuid().ToString("N");
                inf.Node.Board = "board" + Guid.NewGuid().ToString("N");
            }
            return request;
        }
    }

    private async Task<(App app, String token)> CreateControllerAppAsync()
    {
        var name = "logctl_" + Guid.NewGuid().ToString("N")[..8];
        var app = new App { Name = name, Secret = "log-secret-" + Guid.NewGuid().ToString("N")[..6], Enable = true, AllowControlNodes = "*" };
        app.Insert();

        using var http = new HttpClient { BaseAddress = new Uri(_fixture.ServerUrl) };
        var rs = await http.PostAsync<LoginResponse>("app/login", new { AppId = name, Secret = app.Secret });
        Assert.False(rs?.Token.IsNullOrEmpty());
        return (app, rs!.Token!);
    }

    private static void Cleanup(Node? node)
    {
        if (node == null) return;
        NodeOnline.FindByNodeId(node.ID)?.Delete();
        node.Delete();
    }

    private static void CleanupApp(App? app)
    {
        if (app == null) return;
        AppOnline.Delete(AppOnline._.AppId == app.Id);
        app.Delete();
    }
}