1
0

client_node_push_test.go 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. package service
  2. import (
  3. "testing"
  4. "time"
  5. "github.com/mhsanaei/3x-ui/v3/internal/database"
  6. "github.com/mhsanaei/3x-ui/v3/internal/database/model"
  7. )
  8. // TestResetTrafficAcrossNodesPushesConcurrently covers the panel's per-client
  9. // traffic reset, which propagated to nodes one round-trip after another.
  10. func TestResetTrafficAcrossNodesPushesConcurrently(t *testing.T) {
  11. setupBulkDB(t)
  12. startSerializedWriter(t)
  13. const nodes = inboundFanoutConcurrency + 1
  14. const email = "reset@x"
  15. bar := newApplyBarrier(inboundFanoutConcurrency)
  16. seedClientAcrossNodes(t, bar, nodes, 46801, email, "88888888-1111-2222-3333-444444444444")
  17. bar.arm()
  18. if _, err := (&ClientService{}).ResetTrafficByEmail(&InboundService{}, email); err != nil {
  19. t.Fatalf("ResetTrafficByEmail across %d node inbounds: %v", nodes, err)
  20. }
  21. if got := bar.maxPar.Load(); got != inboundFanoutConcurrency {
  22. t.Fatalf("peak node pushes in flight = %d, want overlap at the %d cap (barrier timed out: %v)",
  23. got, inboundFanoutConcurrency, bar.expired.Load())
  24. }
  25. }
  26. // TestNodePushGivesUpBeforeTheRemoteTimeout pins that an edit gives up on a node
  27. // that hangs on the push at the deadline, leaving it dirty for the reconcile.
  28. func TestNodePushGivesUpBeforeTheRemoteTimeout(t *testing.T) {
  29. setupBulkDB(t)
  30. startSerializedWriter(t)
  31. useTestRuntimeManager(t)
  32. db := database.GetDB()
  33. const uuid = "77777777-1111-2222-3333-444444444444"
  34. f := newFakeNodeHTTP(t)
  35. ib := realNodeInbound(t, f, 53100, []model.Client{{Email: "hung@x", ID: uuid, SubID: "sub-hung", Enable: true}})
  36. rec := lookupClientRecord(t, "hung@x")
  37. f.setHold(true)
  38. start := time.Now()
  39. if _, err := (&ClientService{}).Update(&InboundService{}, rec.Id, model.Client{
  40. Email: "hung@x", ID: uuid, SubID: "sub-hung", Enable: true, Comment: "edited",
  41. }, 0); err != nil {
  42. t.Fatalf("Update against a hung node: %v", err)
  43. }
  44. elapsed := time.Since(start)
  45. // Both bounds: no push at all would also finish fast and leave the node dirty.
  46. if got := f.hitCount("/clients/update/"); got != 1 {
  47. t.Fatalf("clients/update requests reaching the hung node = %d, want exactly 1", got)
  48. }
  49. if elapsed < nodeClientPushTimeout-200*time.Millisecond {
  50. t.Fatalf("edit returned after %v, before the %v push deadline: the push was never awaited", elapsed, nodeClientPushTimeout)
  51. }
  52. if elapsed >= 2*nodeClientPushTimeout {
  53. t.Fatalf("edit took %v against a hung node: it waited out the remote timeout instead of the %v push deadline", elapsed, nodeClientPushTimeout)
  54. }
  55. var node model.Node
  56. if err := db.Where("id = ?", *ib.NodeID).First(&node).Error; err != nil {
  57. t.Fatalf("read node: %v", err)
  58. }
  59. if !node.ConfigDirty {
  60. t.Fatal("the node whose push timed out must stay dirty, or nothing ever converges it")
  61. }
  62. }
  63. // TestBulkDeleteStopsPushingToAHungNode pins the batch circuit-break: once one
  64. // push times out, the rest of the batch defers to the reconcile as well.
  65. func TestBulkDeleteStopsPushingToAHungNode(t *testing.T) {
  66. setupBulkDB(t)
  67. startSerializedWriter(t)
  68. useTestRuntimeManager(t)
  69. f := newFakeNodeHTTP(t)
  70. realNodeInbound(t, f, 53200, []model.Client{
  71. {Email: "b1@x", ID: "66666666-1111-2222-3333-444444444441", SubID: "sub-b1", Enable: true},
  72. {Email: "b2@x", ID: "66666666-1111-2222-3333-444444444442", SubID: "sub-b2", Enable: true},
  73. {Email: "b3@x", ID: "66666666-1111-2222-3333-444444444443", SubID: "sub-b3", Enable: true},
  74. })
  75. f.setHold(true)
  76. start := time.Now()
  77. if _, _, err := (&ClientService{}).BulkDelete(&InboundService{}, []string{"b1@x", "b2@x", "b3@x"}, false); err != nil {
  78. t.Fatalf("BulkDelete against a hung node: %v", err)
  79. }
  80. elapsed := time.Since(start)
  81. if got := f.hitCount("/clients/del/"); got != 1 {
  82. t.Fatalf("clients/del requests sent to the hung node = %d, want 1: the batch kept paying a deadline per client", got)
  83. }
  84. if elapsed >= 2*nodeClientPushTimeout {
  85. t.Fatalf("deleting 3 clients took %v against a hung node, want a single %v deadline, not one per client", elapsed, nodeClientPushTimeout)
  86. }
  87. }