consul_resolver_test.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496
  1. package upstream
  2. import (
  3. "testing"
  4. )
  5. // TestParseProxyTargetsWithConsulResolver tests parsing of nginx config with consul DNS resolver
  6. func TestParseProxyTargetsWithConsulResolver(t *testing.T) {
  7. config := `upstream redacted-net {
  8. zone upstream_web 128k;
  9. resolver 127.0.0.1:8600 valid=5s;
  10. resolver_timeout 2s;
  11. server service.consul service=redacted-net resolve;
  12. }
  13. server {
  14. listen 80;
  15. listen [::]:80;
  16. server_name redacted.net;
  17. location / {
  18. proxy_pass http://redacted-net;
  19. }
  20. }`
  21. targets := ParseProxyTargetsFromRawContent(config)
  22. // Print actual results for debugging
  23. t.Logf("Found %d targets:", len(targets))
  24. for i, target := range targets {
  25. t.Logf("Target %d: Host=%s, Port=%s, Type=%s, Resolver=%s, IsConsul=%v",
  26. i+1, target.Host, target.Port, target.Type, target.Resolver, target.IsConsul)
  27. }
  28. // Expected behavior:
  29. // - Should parse "service.consul" as host with dynamic port
  30. // - Should identify this as an upstream target with consul service discovery
  31. // - Should capture resolver information
  32. // - proxy_pass http://redacted-net should be ignored since it references upstream
  33. expectedTargets := []ProxyTarget{
  34. {
  35. Host: "service.consul",
  36. Port: "dynamic",
  37. Type: "upstream",
  38. Resolver: "127.0.0.1:8600",
  39. IsConsul: true,
  40. ServiceURL: "service.consul service=redacted-net resolve",
  41. },
  42. }
  43. if len(targets) != len(expectedTargets) {
  44. t.Errorf("Expected %d targets, got %d", len(expectedTargets), len(targets))
  45. return
  46. }
  47. // Create a map for easier comparison
  48. targetMap := make(map[string]ProxyTarget)
  49. for _, target := range targets {
  50. key := target.Host + ":" + target.Port + ":" + target.Type + ":" + target.Resolver
  51. if target.IsConsul {
  52. key += ":consul:" + target.ServiceURL
  53. }
  54. targetMap[key] = target
  55. }
  56. for _, expected := range expectedTargets {
  57. key := expected.Host + ":" + expected.Port + ":" + expected.Type + ":" + expected.Resolver
  58. if expected.IsConsul {
  59. key += ":consul:" + expected.ServiceURL
  60. }
  61. if _, found := targetMap[key]; !found {
  62. t.Errorf("Expected target not found: %+v", expected)
  63. }
  64. }
  65. }
  66. // TestConsulResolverExtractServiceName tests service name extraction
  67. func TestConsulResolverExtractServiceName(t *testing.T) {
  68. resolver := NewConsulResolver("127.0.0.1:8600")
  69. tests := []struct {
  70. serviceURL string
  71. expectedName string
  72. }{
  73. {
  74. serviceURL: "service.consul service=redacted-net resolve",
  75. expectedName: "redacted-net",
  76. },
  77. {
  78. serviceURL: "service.consul service=web-service resolve",
  79. expectedName: "web-service",
  80. },
  81. {
  82. serviceURL: "service.consul service=api-backend resolve",
  83. expectedName: "api-backend",
  84. },
  85. {
  86. serviceURL: "my-service.service.consul",
  87. expectedName: "my-service",
  88. },
  89. {
  90. serviceURL: "invalid-format",
  91. expectedName: "",
  92. },
  93. }
  94. for _, test := range tests {
  95. result := resolver.extractServiceName(test.serviceURL)
  96. if result != test.expectedName {
  97. t.Errorf("extractServiceName(%q) = %q, expected %q", test.serviceURL, result, test.expectedName)
  98. }
  99. }
  100. }
  101. // TestConsulResolverResolveService tests the actual resolution functionality
  102. func TestConsulResolverResolveService(t *testing.T) {
  103. // Test with a mock resolver that should fail (127.0.0.1:8600 is consul default but likely not running)
  104. t.Run("ResolutionWithMockConsul", func(t *testing.T) {
  105. resolver := NewConsulResolver("127.0.0.1:8600")
  106. addresses, err := resolver.ResolveService("service.consul service=test-service resolve")
  107. // We expect this to fail since there's no real consul server
  108. if err == nil {
  109. t.Logf("Unexpected success: resolved addresses %v (maybe there's a real consul server?)", addresses)
  110. } else {
  111. t.Logf("Expected failure: %v", err)
  112. }
  113. })
  114. // Test with invalid service URL
  115. t.Run("InvalidServiceURL", func(t *testing.T) {
  116. resolver := NewConsulResolver("127.0.0.1:8600")
  117. addresses, err := resolver.ResolveService("invalid-service-url")
  118. if err == nil {
  119. t.Errorf("Expected error for invalid service URL, got addresses: %v", addresses)
  120. }
  121. if len(addresses) != 0 {
  122. t.Errorf("Expected no addresses for invalid service URL, got %v", addresses)
  123. }
  124. })
  125. // Test with invalid resolver address
  126. t.Run("InvalidResolverAddress", func(t *testing.T) {
  127. resolver := NewConsulResolver("192.168.254.254:8600") // Unreachable IP
  128. addresses, err := resolver.ResolveService("service.consul service=test-service resolve")
  129. // Should fail due to unreachable resolver
  130. if err == nil {
  131. t.Errorf("Expected error for unreachable resolver, got addresses: %v", addresses)
  132. }
  133. })
  134. // Test service name extraction edge cases
  135. t.Run("ServiceNameExtractionEdgeCases", func(t *testing.T) {
  136. resolver := NewConsulResolver("127.0.0.1:8600")
  137. testCases := []struct {
  138. serviceURL string
  139. expectedName string
  140. }{
  141. {"", ""},
  142. {"service.consul", ""},
  143. {"service.consul resolve", ""},
  144. {"service.consul service= resolve", ""},
  145. {"service.consul service= resolve", ""}, // Empty service name
  146. {"my-service.service.consul", "my-service"},
  147. {"complex-service-name.service.consul", "complex-service-name"},
  148. }
  149. for _, tc := range testCases {
  150. result := resolver.extractServiceName(tc.serviceURL)
  151. if result != tc.expectedName {
  152. t.Errorf("extractServiceName(%q) = %q, expected %q", tc.serviceURL, result, tc.expectedName)
  153. }
  154. }
  155. })
  156. }
  157. // TestTestConsulTargets tests the new dedicated consul testing function
  158. func TestTestConsulTargets(t *testing.T) {
  159. // Test 1: Valid consul targets with resolver
  160. t.Run("ValidConsulTargets", func(t *testing.T) {
  161. consulTargets := []ProxyTarget{
  162. {
  163. Host: "service.consul",
  164. Port: "dynamic",
  165. Type: "upstream",
  166. Resolver: "127.0.0.1:8600",
  167. IsConsul: true,
  168. ServiceURL: "service.consul service=test-service resolve",
  169. },
  170. }
  171. results := TestConsulTargets(consulTargets)
  172. // Should have exactly 1 result
  173. if len(results) != 1 {
  174. t.Errorf("Expected 1 result, got %d", len(results))
  175. }
  176. // Check the result exists with correct key
  177. key := "service.consul:dynamic"
  178. if status, found := results[key]; found {
  179. // The status doesn't matter much (likely offline without real consul)
  180. // What matters is that the function processes the target correctly
  181. t.Logf("Consul target %s processed: Online=%v, Latency=%.2f", key, status.Online, status.Latency)
  182. } else {
  183. t.Errorf("Expected result for key %s not found", key)
  184. }
  185. })
  186. // Test 2: Consul target without resolver should be marked offline
  187. t.Run("ConsulTargetWithoutResolver", func(t *testing.T) {
  188. consulTargets := []ProxyTarget{
  189. {
  190. Host: "service.consul",
  191. Port: "dynamic",
  192. Type: "upstream",
  193. // No resolver - should be marked offline immediately
  194. IsConsul: true,
  195. ServiceURL: "service.consul service=no-resolver resolve",
  196. },
  197. }
  198. results := TestConsulTargets(consulTargets)
  199. if len(results) != 1 {
  200. t.Errorf("Expected 1 result, got %d", len(results))
  201. }
  202. key := "service.consul:dynamic"
  203. if status, found := results[key]; found {
  204. if status.Online {
  205. t.Errorf("Expected consul target without resolver to be offline, but it's online")
  206. }
  207. if status.Latency != 0 {
  208. t.Errorf("Expected latency to be 0 for offline target, got %.2f", status.Latency)
  209. }
  210. } else {
  211. t.Errorf("Expected result for key %s not found", key)
  212. }
  213. })
  214. // Test 3: Multiple consul targets with different resolvers
  215. t.Run("MultipleConsulTargetsWithDifferentResolvers", func(t *testing.T) {
  216. consulTargets := []ProxyTarget{
  217. {
  218. Host: "web-service.consul",
  219. Port: "dynamic",
  220. Type: "upstream",
  221. Resolver: "127.0.0.1:8600",
  222. IsConsul: true,
  223. ServiceURL: "service.consul service=web-service resolve",
  224. },
  225. {
  226. Host: "api-service.consul",
  227. Port: "dynamic",
  228. Type: "upstream",
  229. Resolver: "127.0.0.1:8500", // Different resolver
  230. IsConsul: true,
  231. ServiceURL: "service.consul service=api-service resolve",
  232. },
  233. }
  234. results := TestConsulTargets(consulTargets)
  235. // Should have 2 results
  236. if len(results) != 2 {
  237. t.Errorf("Expected 2 results, got %d", len(results))
  238. }
  239. // Check both results exist
  240. expectedKeys := []string{
  241. "web-service.consul:dynamic",
  242. "api-service.consul:dynamic",
  243. }
  244. for _, key := range expectedKeys {
  245. if _, found := results[key]; !found {
  246. t.Errorf("Expected result for key %s not found", key)
  247. }
  248. }
  249. })
  250. // Test 4: Empty consul targets should return empty results
  251. t.Run("EmptyConsulTargets", func(t *testing.T) {
  252. consulTargets := []ProxyTarget{}
  253. results := TestConsulTargets(consulTargets)
  254. if len(results) != 0 {
  255. t.Errorf("Expected 0 results for empty targets, got %d", len(results))
  256. }
  257. })
  258. }
  259. // TestSimplifiedArchitecture tests the new simplified architecture
  260. func TestSimplifiedArchitecture(t *testing.T) {
  261. service := GetUpstreamService()
  262. service.ClearTargets()
  263. // Mix of traditional and consul targets
  264. mixedTargets := []ProxyTarget{
  265. // Traditional targets
  266. {Host: "127.0.0.1", Port: "80", Type: "upstream"},
  267. {Host: "192.168.1.100", Port: "8080", Type: "upstream"},
  268. // Consul targets
  269. {
  270. Host: "service.consul",
  271. Port: "dynamic",
  272. Type: "upstream",
  273. Resolver: "127.0.0.1:8600",
  274. IsConsul: true,
  275. ServiceURL: "service.consul service=my-service resolve",
  276. },
  277. }
  278. service.updateTargetsFromConfig("test-config.conf", mixedTargets)
  279. // Verify targets are correctly stored
  280. service.targetsMutex.RLock()
  281. traditionalCount := 0
  282. consulCount := 0
  283. for _, targetInfo := range service.targets {
  284. if targetInfo.ProxyTarget.IsConsul {
  285. consulCount++
  286. } else {
  287. traditionalCount++
  288. }
  289. }
  290. service.targetsMutex.RUnlock()
  291. if traditionalCount != 2 {
  292. t.Errorf("Expected 2 traditional targets, got %d", traditionalCount)
  293. }
  294. if consulCount != 1 {
  295. t.Errorf("Expected 1 consul target, got %d", consulCount)
  296. }
  297. t.Logf("Architecture correctly separated %d traditional and %d consul targets", traditionalCount, consulCount)
  298. // Clean up
  299. service.ClearTargets()
  300. }
  301. // TestEnhancedAvailabilityTest tests the enhanced availability testing with mixed targets
  302. func TestEnhancedAvailabilityTest(t *testing.T) {
  303. targets := []ProxyTarget{
  304. // Regular target
  305. {
  306. Host: "127.0.0.1",
  307. Port: "22", // SSH port might be available
  308. Type: "upstream",
  309. },
  310. // Consul target (will fail since no real consul)
  311. {
  312. Host: "service.consul",
  313. Port: "dynamic",
  314. Type: "upstream",
  315. Resolver: "127.0.0.1:8600",
  316. IsConsul: true,
  317. ServiceURL: "service.consul service=test-service resolve",
  318. },
  319. // Invalid regular target
  320. {
  321. Host: "192.168.254.254",
  322. Port: "9999",
  323. Type: "upstream",
  324. },
  325. }
  326. results := EnhancedAvailabilityTest(targets)
  327. t.Logf("Found %d test results:", len(results))
  328. for key, status := range results {
  329. t.Logf("Target %s: Online=%v, Latency=%.2f", key, status.Online, status.Latency)
  330. }
  331. // Verify we have results for all targets
  332. expectedKeys := []string{
  333. "127.0.0.1:22",
  334. "service.consul:dynamic",
  335. "192.168.254.254:9999",
  336. }
  337. for _, key := range expectedKeys {
  338. if _, found := results[key]; !found {
  339. t.Errorf("Expected result for key %s not found", key)
  340. }
  341. }
  342. // Test that consul target is processed (result doesn't matter, we just verify the flow works)
  343. if status, found := results["service.consul:dynamic"]; found {
  344. t.Logf("Consul target processed: Online=%v, Latency=%.2f", status.Online, status.Latency)
  345. }
  346. // Test that unreachable target is properly handled
  347. if status, found := results["192.168.254.254:9999"]; found {
  348. // This should be offline due to unreachable IP, but we verify the function handles it
  349. t.Logf("Unreachable target processed: Online=%v, Latency=%.2f", status.Online, status.Latency)
  350. // Most likely offline, but we don't assume - we just verify it was processed
  351. }
  352. }
  353. // TestTraditionalAvailabilityTestUsage tests that traditional AvailabilityTest is used when no consul targets
  354. func TestTraditionalAvailabilityTestUsage(t *testing.T) {
  355. // Test with only traditional targets
  356. targets := []ProxyTarget{
  357. {
  358. Host: "127.0.0.1",
  359. Port: "80",
  360. Type: "upstream",
  361. },
  362. {
  363. Host: "192.168.254.254",
  364. Port: "9999",
  365. Type: "upstream",
  366. },
  367. }
  368. // Test enhanced version with traditional targets only
  369. enhancedResults := EnhancedAvailabilityTest(targets)
  370. // Test traditional version directly
  371. traditionalKeys := []string{"127.0.0.1:80", "192.168.254.254:9999"}
  372. traditionalResults := AvailabilityTest(traditionalKeys)
  373. t.Logf("Enhanced results: %d items", len(enhancedResults))
  374. t.Logf("Traditional results: %d items", len(traditionalResults))
  375. // Both should have same number of results
  376. if len(enhancedResults) != len(traditionalResults) {
  377. t.Errorf("Expected same number of results, enhanced=%d, traditional=%d",
  378. len(enhancedResults), len(traditionalResults))
  379. return
  380. }
  381. // Results should be consistent
  382. for key, traditionalStatus := range traditionalResults {
  383. if enhancedStatus, found := enhancedResults[key]; found {
  384. if traditionalStatus.Online != enhancedStatus.Online {
  385. t.Errorf("Inconsistent online status for %s: traditional=%v, enhanced=%v",
  386. key, traditionalStatus.Online, enhancedStatus.Online)
  387. }
  388. } else {
  389. t.Errorf("Key %s missing in enhanced results", key)
  390. }
  391. }
  392. t.Logf("Enhanced test correctly delegated to traditional test for non-consul targets")
  393. }
  394. // TestUpstreamServiceSimplifiedFlow tests the new simplified flow in UpstreamService
  395. func TestUpstreamServiceSimplifiedFlow(t *testing.T) {
  396. service := GetUpstreamService()
  397. service.ClearTargets()
  398. // Add mixed targets
  399. mixedTargets := []ProxyTarget{
  400. {Host: "127.0.0.1", Port: "80", Type: "upstream"},
  401. {
  402. Host: "service.consul",
  403. Port: "dynamic",
  404. Type: "upstream",
  405. Resolver: "127.0.0.1:8600",
  406. IsConsul: true,
  407. ServiceURL: "service.consul service=test resolve",
  408. },
  409. }
  410. service.updateTargetsFromConfig("test-config.conf", mixedTargets)
  411. // This would trigger the simplified flow
  412. service.PerformAvailabilityTest()
  413. results := service.GetAvailabilityMap()
  414. t.Logf("Simplified flow generated %d results:", len(results))
  415. for key, status := range results {
  416. t.Logf("Result %s: Online=%v, Latency=%.2f", key, status.Online, status.Latency)
  417. }
  418. // Should have results for both targets
  419. expectedKeys := []string{"127.0.0.1:80", "service.consul:dynamic"}
  420. for _, key := range expectedKeys {
  421. if _, found := results[key]; !found {
  422. t.Errorf("Expected result for key %s not found", key)
  423. }
  424. }
  425. t.Logf("Simplified architecture correctly processed mixed targets")
  426. // Clean up
  427. service.ClearTargets()
  428. }