smoke.lua 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. --[[
  2. tests/smoke.lua — End-to-end smoke test
  3. Boots the daemon in dry-run mode with a fake UCI config; runs 2 cycles,
  4. verifies status.json + control.json round-trip via the ctl module.
  5. ]]
  6. package.path = package.path .. ";/usr/lib/lua/5.1/?.lua;./src/?.lua"
  7. local config = require("balancerlite.config")
  8. local ctl = require("balancerlite.ctl")
  9. local passed = 0
  10. local failed = 0
  11. local function check(name, ok, detail)
  12. if ok then
  13. passed = passed + 1
  14. print(" PASS " .. name)
  15. else
  16. failed = failed + 1
  17. print(" FAIL " .. name .. (detail and (" " .. detail) or ""))
  18. end
  19. end
  20. -- -----------------------------------------------------------------
  21. -- Setup: write a minimal UCI config to a temp dir.
  22. -- -----------------------------------------------------------------
  23. local dir = "/tmp/bl_smoke_" .. tostring(os.time()) .. "_" ..
  24. tostring(math.random(1000000))
  25. os.execute("mkdir -p " .. dir)
  26. local cfg_path = dir .. "/balancerlite"
  27. local f = io.open(cfg_path, "w")
  28. f:write([[
  29. config general 'general'
  30. option host 'smoke-test'
  31. option dry_run '1'
  32. option log_level 'info'
  33. config health 'health'
  34. option probe_interval '1s'
  35. option window_size '5'
  36. option down_threshold '2'
  37. option up_threshold '3'
  38. option icmp_timeout '1s'
  39. option tcp_timeout '2s'
  40. option dns_timeout '2s'
  41. option dns_probe_domain 'example.com'
  42. option icmp_enabled '0'
  43. option tcp_enabled '0'
  44. option dns_enabled '0'
  45. config wan 'wan_a'
  46. option interface 'lo'
  47. option address '127.0.0.1/32'
  48. option gateway '127.0.0.1'
  49. config wan 'wan_b'
  50. option interface 'lo'
  51. option address '127.0.0.1/32'
  52. option gateway '127.0.0.1'
  53. config routing 'routing'
  54. option table_a '100'
  55. option table_b '101'
  56. option mark_active_a '0x1'
  57. option mark_active_b '0x2'
  58. config wireguard 'wg'
  59. option enabled '0'
  60. option interface 'wg0'
  61. config notifier 'notifier'
  62. option webhook_url ''
  63. option webhook_secret ''
  64. option batch_window '0s'
  65. option max_attempts '1'
  66. option initial_backoff '1s'
  67. option max_backoff '1s'
  68. option outbox_capacity '100'
  69. option cb_threshold '5'
  70. option cb_cooldown '60s'
  71. config flap 'flap'
  72. option switch_count '3'
  73. option window '60s'
  74. option recovery_grace '60s'
  75. config features 'features'
  76. option hot_reload '0'
  77. option trace_ids '0'
  78. config store 'store'
  79. option state_dir ']] .. dir .. [['
  80. option retention_days '30'
  81. ]])
  82. f:close()
  83. -- -----------------------------------------------------------------
  84. -- TEST 1: config loads
  85. -- -----------------------------------------------------------------
  86. print("\n[1] config load")
  87. local cfg, err = config.load(cfg_path)
  88. check("config loads", cfg ~= nil, tostring(err))
  89. check("dry_run=true", cfg.general.dry_run == true)
  90. check("host=smoke-test", cfg.general.host == "smoke-test")
  91. check("state_dir matches", cfg.store.state_dir == dir)
  92. check("icmp disabled", cfg.health.icmp_enabled == false)
  93. check("tcp disabled", cfg.health.tcp_enabled == false)
  94. -- -----------------------------------------------------------------
  95. -- TEST 2: ctl round-trip in our temp dir
  96. -- -----------------------------------------------------------------
  97. print("\n[2] ctl round-trip in " .. dir)
  98. local rc = ctl.cmd_switch(dir, "wan-b")
  99. check("ctl switch rc=0", rc == 0)
  100. local req = ctl.read_control(dir)
  101. check("control file written", req ~= nil and req.kind == "switch" and
  102. req.target == "wan-b")
  103. ctl.clear_control(dir)
  104. check("control file cleared", io.open(dir .. "/control.json", "r") == nil)
  105. -- status file should NOT exist yet (daemon hasn't been started)
  106. check("no status yet", io.open(dir .. "/status.json", "r") == nil)
  107. -- Write a fake status file (as the daemon would)
  108. local sf = io.open(dir .. "/status.json", "w")
  109. sf:write('{"daemon":true,"state":"WAN_A_PRIMARY","active_wan":"wan-a",' ..
  110. '"host":"smoke-test","cycles":3,"updated_ts":' ..
  111. tostring(os.time()) .. '}\n')
  112. sf:close()
  113. local st = ctl.read_status(dir)
  114. check("status readable", st ~= nil and st.daemon == true)
  115. check("status.state=WAN_A_PRIMARY", st.state == "WAN_A_PRIMARY")
  116. check("status.active_wan=wan-a", st.active_wan == "wan-a")
  117. local rendered = ctl.render_status(st)
  118. check("rendered contains WAN_A_PRIMARY",
  119. rendered:match("WAN_A_PRIMARY"))
  120. check("rendered contains 'alive'", rendered:match("alive"))
  121. -- -----------------------------------------------------------------
  122. -- TEST 3: ctl cmd_status returns rc=0 when status file exists
  123. -- -----------------------------------------------------------------
  124. print("\n[3] cmd_status with status file")
  125. local rc2 = ctl.cmd_status(dir)
  126. check("rc=0", rc2 == 0)
  127. -- -----------------------------------------------------------------
  128. -- TEST 4: ctl cmd_events with no events
  129. -- -----------------------------------------------------------------
  130. print("\n[4] cmd_events with empty store")
  131. local rc3 = ctl.cmd_events(dir, 10)
  132. check("rc=0", rc3 == 0)
  133. -- -----------------------------------------------------------------
  134. -- Cleanup
  135. -- -----------------------------------------------------------------
  136. os.execute("rm -rf " .. dir)
  137. print(string.format("\n=== smoke: %d/%d passed ===", passed, passed + failed))
  138. if failed > 0 then os.exit(1) end