节点在线、应用在线、配置在线使用令牌查询
大石头 authored at 2021-12-16 19:49:30
3.93 KiB
Stardust
using System;
using System.Diagnostics;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using NewLife;
using NewLife.Log;
using NewLife.Security;
using Stardust.Data.Models;
using Stardust.Data.Nodes;
using Stardust.Server.Services;
using Xunit;

namespace ServerTest.Services;

/// <summary>Redis 监控服务容错测试。覆盖采集失败计数、连续失败后自动停用节点与启停生命周期(DBM-6)</summary>
public class RedisMonitorFailureTests
{
    /// <summary>调用私有的采集循环,模拟一次定时器触发</summary>
    private static void RunTrace(RedisService service) =>
        typeof(RedisService).GetMethod("DoTraceNode", BindingFlags.NonPublic | BindingFlags.Instance).Invoke(service, [null]);

    /// <summary>绕过实体缓存直接查库读取启用状态</summary>
    private static Boolean? ReadEnable(Int32 id)
    {
        var dt = RedisNode.Meta.Session.Query($"select Enable from {RedisNode.Meta.TableName} where Id={id}");
        if (dt == null || dt.Rows.Count == 0) return null;

        return dt.Rows[0][0].ToBoolean();
    }

    [Fact(DisplayName = "采集连续失败五次后自动停用节点")]
    public async Task TraceNode_FailFiveTimes_DisablesNode()
    {
        var service = new RedisService(null, Logger.Null);

        var node = new RedisNode
        {
            Name = "redis-" + Rand.NextString(6),
            Server = "127.0.0.1:2",
            Enable = true,
            ScanQueue = false,
        };
        node.Insert();

        try
        {
            // 失败计数在异常分支内递增,第 5 次失败后计数达到阈值,第 6 次进入停用分支
            for (var i = 0; i < 6; i++) RunTrace(service);

            // 停用是异步落库,轮询等待
            var sw = Stopwatch.StartNew();
            Boolean? enable = null;
            while (sw.ElapsedMilliseconds < 10000)
            {
                enable = ReadEnable(node.Id);
                if (enable == false) break;

                await Task.Delay(100);
            }

            Assert.False(enable);
        }
        finally
        {
            RedisNode.FindById(node.Id)?.Delete();
        }
    }

    [Fact(DisplayName = "采集异常会计入失败次数")]
    public void TraceNode_Error_IncrementsCounter()
    {
        var service = new RedisService(null, Logger.Null);

        var node = new RedisNode
        {
            Name = "redis-" + Rand.NextString(6),
            Server = "127.0.0.1:3",
            Enable = true,
            ScanQueue = false,
        };
        node.Insert();

        try
        {
            RunTrace(service);

            var cache = (NewLife.Caching.MemoryCache)typeof(RedisService)
                .GetField("_cache", BindingFlags.NonPublic | BindingFlags.Instance)!.GetValue(service);
            var errors = cache.Get<Int64>($"DoTraceNode:{node.Id}");

            Assert.True(errors >= 1, $"失败次数应大于等于1,实际 {errors}");
        }
        finally
        {
            RedisNode.FindById(node.Id)?.Delete();
        }
    }

    [Fact(DisplayName = "未启用节点不参与采集,状态保持不变")]
    public void TraceNode_DisabledNode_Skip()
    {
        var service = new RedisService(null, Logger.Null);

        var node = new RedisNode
        {
            Name = "redis-" + Rand.NextString(6),
            Server = "127.0.0.1:1",
            Enable = false,
        };
        node.Insert();

        try
        {
            RunTrace(service);

            Assert.False(ReadEnable(node.Id));
        }
        finally
        {
            RedisNode.FindById(node.Id)?.Delete();
        }
    }

    [Fact(DisplayName = "采集启动与停止不抛异常")]
    public async Task StartStop_NoThrow()
    {
        var service = new RedisService(null, Logger.Null) { Period = 60 };

        await service.StartAsync(CancellationToken.None);
        await service.StopAsync(CancellationToken.None);
    }
}