dynamic_resolver_test.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683
  1. package upstream
  2. import (
  3. "context"
  4. "fmt"
  5. "net"
  6. "sort"
  7. "strings"
  8. "testing"
  9. )
  10. // MockDNSServer simulates DNS responses for testing
  11. type MockDNSServer struct {
  12. srvRecords map[string][]*net.SRV
  13. aRecords map[string][]net.IPAddr
  14. }
  15. // NewMockDNSServer creates a mock DNS server for testing
  16. func NewMockDNSServer() *MockDNSServer {
  17. return &MockDNSServer{
  18. srvRecords: make(map[string][]*net.SRV),
  19. aRecords: make(map[string][]net.IPAddr),
  20. }
  21. }
  22. // AddSRVRecord adds a SRV record to the mock DNS server
  23. func (m *MockDNSServer) AddSRVRecord(domain string, priority, weight uint16, port uint16, target string) {
  24. m.srvRecords[domain] = append(m.srvRecords[domain], &net.SRV{
  25. Priority: priority,
  26. Weight: weight,
  27. Port: port,
  28. Target: target,
  29. })
  30. }
  31. // AddARecord adds an A record to the mock DNS server
  32. func (m *MockDNSServer) AddARecord(domain string, ip string) {
  33. m.aRecords[domain] = append(m.aRecords[domain], net.IPAddr{
  34. IP: net.ParseIP(ip),
  35. })
  36. }
  37. // MockResolver is a custom resolver that uses our mock DNS server
  38. type MockResolver struct {
  39. mockServer *MockDNSServer
  40. }
  41. // LookupSRV simulates SRV record lookup with proper priority sorting
  42. func (mr *MockResolver) LookupSRV(ctx context.Context, service, proto, name string) (string, []*net.SRV, error) {
  43. domain := name
  44. if service != "" || proto != "" {
  45. domain = fmt.Sprintf("_%s._%s.%s", service, proto, name)
  46. }
  47. if records, exists := mr.mockServer.srvRecords[domain]; exists {
  48. // Sort SRV records by priority (lowest first), then by weight (highest first)
  49. // This follows RFC 2782 and nginx behavior
  50. sortedRecords := make([]*net.SRV, len(records))
  51. copy(sortedRecords, records)
  52. sort.Slice(sortedRecords, func(i, j int) bool {
  53. if sortedRecords[i].Priority != sortedRecords[j].Priority {
  54. return sortedRecords[i].Priority < sortedRecords[j].Priority
  55. }
  56. // For same priority, higher weight comes first (but this is simplified for testing)
  57. return sortedRecords[i].Weight > sortedRecords[j].Weight
  58. })
  59. return "", sortedRecords, nil
  60. }
  61. return "", nil, fmt.Errorf("no SRV records for %s", domain)
  62. }
  63. // LookupIPAddr simulates A record lookup
  64. func (mr *MockResolver) LookupIPAddr(ctx context.Context, host string) ([]net.IPAddr, error) {
  65. if records, exists := mr.mockServer.aRecords[host]; exists {
  66. return records, nil
  67. }
  68. return nil, fmt.Errorf("no A records for %s", host)
  69. }
  70. // TestParseServiceURL tests the parseServiceURL function with nginx compliance
  71. func TestParseServiceURL(t *testing.T) {
  72. tests := []struct {
  73. name string
  74. input string
  75. expectedErr bool
  76. expected *ServiceInfo
  77. }{
  78. {
  79. name: "Valid nginx service URL - simple service name",
  80. input: "backend.example.com service=http resolve",
  81. expected: &ServiceInfo{
  82. Hostname: "backend.example.com",
  83. ServiceName: "http",
  84. },
  85. },
  86. {
  87. name: "Valid nginx service URL - service name with underscores",
  88. input: "backend.example.com service=_http._tcp resolve",
  89. expected: &ServiceInfo{
  90. Hostname: "backend.example.com",
  91. ServiceName: "_http._tcp",
  92. },
  93. },
  94. {
  95. name: "Valid nginx service URL - service name with dots",
  96. input: "example.com service=server1.backend resolve",
  97. expected: &ServiceInfo{
  98. Hostname: "example.com",
  99. ServiceName: "server1.backend",
  100. },
  101. },
  102. {
  103. name: "Consul service example",
  104. input: "service.consul service=web-service resolve",
  105. expected: &ServiceInfo{
  106. Hostname: "service.consul",
  107. ServiceName: "web-service",
  108. },
  109. },
  110. {
  111. name: "Empty input",
  112. input: "",
  113. expectedErr: true,
  114. },
  115. {
  116. name: "Missing resolve parameter",
  117. input: "backend.example.com service=http",
  118. expectedErr: true,
  119. },
  120. {
  121. name: "Missing service parameter",
  122. input: "backend.example.com resolve",
  123. expectedErr: true,
  124. },
  125. {
  126. name: "Empty service name",
  127. input: "backend.example.com service= resolve",
  128. expectedErr: true,
  129. },
  130. {
  131. name: "Only hostname",
  132. input: "backend.example.com",
  133. expectedErr: true,
  134. },
  135. }
  136. resolver := NewDynamicResolver("127.0.0.1:8600")
  137. for _, tt := range tests {
  138. t.Run(tt.name, func(t *testing.T) {
  139. result, err := resolver.parseServiceURL(tt.input)
  140. if tt.expectedErr {
  141. if err == nil {
  142. t.Errorf("Expected error but got none")
  143. }
  144. return
  145. }
  146. if err != nil {
  147. t.Errorf("Unexpected error: %v", err)
  148. return
  149. }
  150. if result.Hostname != tt.expected.Hostname {
  151. t.Errorf("Expected hostname %s, got %s", tt.expected.Hostname, result.Hostname)
  152. }
  153. if result.ServiceName != tt.expected.ServiceName {
  154. t.Errorf("Expected service name %s, got %s", tt.expected.ServiceName, result.ServiceName)
  155. }
  156. })
  157. }
  158. }
  159. // TestConstructSRVDomain tests SRV domain construction according to nginx.org rules
  160. func TestConstructSRVDomain(t *testing.T) {
  161. tests := []struct {
  162. name string
  163. input *ServiceInfo
  164. expected string
  165. rule string
  166. }{
  167. {
  168. name: "Rule 1: Service name without dots - http",
  169. input: &ServiceInfo{
  170. Hostname: "backend.example.com",
  171. ServiceName: "http",
  172. },
  173. expected: "_http._tcp.backend.example.com",
  174. rule: "nginx rule 1: no dots, add TCP protocol",
  175. },
  176. {
  177. name: "Rule 1: Service name without dots - https",
  178. input: &ServiceInfo{
  179. Hostname: "api.example.com",
  180. ServiceName: "https",
  181. },
  182. expected: "_https._tcp.api.example.com",
  183. rule: "nginx rule 1: no dots, add TCP protocol",
  184. },
  185. {
  186. name: "Rule 1: Service name without dots - mysql",
  187. input: &ServiceInfo{
  188. Hostname: "db.example.com",
  189. ServiceName: "mysql",
  190. },
  191. expected: "_mysql._tcp.db.example.com",
  192. rule: "nginx rule 1: no dots, add TCP protocol",
  193. },
  194. {
  195. name: "Rule 2: Service name with dots - _http._tcp",
  196. input: &ServiceInfo{
  197. Hostname: "backend.example.com",
  198. ServiceName: "_http._tcp",
  199. },
  200. expected: "_http._tcp.backend.example.com",
  201. rule: "nginx rule 2: contains dots, join directly",
  202. },
  203. {
  204. name: "Rule 2: Service name with dots - server1.backend",
  205. input: &ServiceInfo{
  206. Hostname: "example.com",
  207. ServiceName: "server1.backend",
  208. },
  209. expected: "server1.backend.example.com",
  210. rule: "nginx rule 2: contains dots, join directly",
  211. },
  212. {
  213. name: "Rule 2: Complex service name with underscores and dots",
  214. input: &ServiceInfo{
  215. Hostname: "dc1.consul",
  216. ServiceName: "_api._tcp.production",
  217. },
  218. expected: "_api._tcp.production.dc1.consul",
  219. rule: "nginx rule 2: contains dots, join directly",
  220. },
  221. {
  222. name: "Consul example - simple service",
  223. input: &ServiceInfo{
  224. Hostname: "service.consul",
  225. ServiceName: "web",
  226. },
  227. expected: "_web._tcp.service.consul",
  228. rule: "nginx rule 1: no dots, add TCP protocol",
  229. },
  230. }
  231. resolver := NewDynamicResolver("127.0.0.1:8600")
  232. for _, tt := range tests {
  233. t.Run(tt.name, func(t *testing.T) {
  234. result := resolver.constructSRVDomain(tt.input)
  235. if result != tt.expected {
  236. t.Errorf("Expected SRV domain %s, got %s (rule: %s)", tt.expected, result, tt.rule)
  237. }
  238. })
  239. }
  240. }
  241. // TestNginxOfficialExamples tests the exact examples from nginx.org documentation
  242. func TestNginxOfficialExamples(t *testing.T) {
  243. tests := []struct {
  244. name string
  245. nginxConfig string
  246. expectedQuery string
  247. description string
  248. }{
  249. {
  250. name: "Official Example 1",
  251. nginxConfig: "backend.example.com service=http resolve",
  252. expectedQuery: "_http._tcp.backend.example.com",
  253. description: "To look up _http._tcp.backend.example.com SRV record",
  254. },
  255. {
  256. name: "Official Example 2",
  257. nginxConfig: "backend.example.com service=_http._tcp resolve",
  258. expectedQuery: "_http._tcp.backend.example.com",
  259. description: "Service name already contains dots, join directly",
  260. },
  261. {
  262. name: "Official Example 3",
  263. nginxConfig: "example.com service=server1.backend resolve",
  264. expectedQuery: "server1.backend.example.com",
  265. description: "Service name contains dots, join directly",
  266. },
  267. }
  268. resolver := NewDynamicResolver("127.0.0.1:8600")
  269. for _, tt := range tests {
  270. t.Run(tt.name, func(t *testing.T) {
  271. serviceInfo, err := resolver.parseServiceURL(tt.nginxConfig)
  272. if err != nil {
  273. t.Fatalf("Failed to parse nginx config: %v", err)
  274. }
  275. result := resolver.constructSRVDomain(serviceInfo)
  276. if result != tt.expectedQuery {
  277. t.Errorf("nginx.org example failed: expected %s, got %s (%s)",
  278. tt.expectedQuery, result, tt.description)
  279. }
  280. })
  281. }
  282. }
  283. // TestSRVRecordResolutionWithMockDNS tests actual SRV record resolution using mock DNS
  284. func TestSRVRecordResolutionWithMockDNS(t *testing.T) {
  285. // Create mock DNS server
  286. mockDNS := NewMockDNSServer()
  287. // Add SRV records for _http._tcp.backend.example.com
  288. mockDNS.AddSRVRecord("_http._tcp.backend.example.com", 10, 60, 8080, "web1.backend.example.com")
  289. mockDNS.AddSRVRecord("_http._tcp.backend.example.com", 10, 40, 8080, "web2.backend.example.com")
  290. mockDNS.AddSRVRecord("_http._tcp.backend.example.com", 20, 100, 8080, "web3.backend.example.com")
  291. // Add A records for the targets
  292. mockDNS.AddARecord("web1.backend.example.com", "192.168.1.10")
  293. mockDNS.AddARecord("web2.backend.example.com", "192.168.1.11")
  294. mockDNS.AddARecord("web3.backend.example.com", "192.168.1.12")
  295. t.Run("SRV record resolution", func(t *testing.T) {
  296. mockResolver := &MockResolver{mockServer: mockDNS}
  297. // Test SRV lookup
  298. _, srvRecords, err := mockResolver.LookupSRV(context.Background(), "", "", "_http._tcp.backend.example.com")
  299. if err != nil {
  300. t.Fatalf("SRV lookup failed: %v", err)
  301. }
  302. if len(srvRecords) != 3 {
  303. t.Errorf("Expected 3 SRV records, got %d", len(srvRecords))
  304. }
  305. // Verify priority ordering (lowest priority first) and weight ordering (highest weight first within same priority)
  306. expectedPriorities := []uint16{10, 10, 20}
  307. expectedWeights := []uint16{60, 40, 100} // For priorities [10, 10, 20], weights should be [60, 40, 100]
  308. expectedTargets := []string{"web1.backend.example.com", "web2.backend.example.com", "web3.backend.example.com"}
  309. for i, srv := range srvRecords {
  310. if srv.Priority != expectedPriorities[i] {
  311. t.Errorf("Expected priority %d at index %d, got %d", expectedPriorities[i], i, srv.Priority)
  312. }
  313. if srv.Weight != expectedWeights[i] {
  314. t.Errorf("Expected weight %d at index %d, got %d", expectedWeights[i], i, srv.Weight)
  315. }
  316. if srv.Target != expectedTargets[i] {
  317. t.Errorf("Expected target %s at index %d, got %s", expectedTargets[i], i, srv.Target)
  318. }
  319. }
  320. // Test A record resolution for each target
  321. for _, srv := range srvRecords {
  322. ips, err := mockResolver.LookupIPAddr(context.Background(), srv.Target)
  323. if err != nil {
  324. t.Errorf("A record lookup failed for %s: %v", srv.Target, err)
  325. continue
  326. }
  327. if len(ips) != 1 {
  328. t.Errorf("Expected 1 IP for %s, got %d", srv.Target, len(ips))
  329. }
  330. }
  331. })
  332. }
  333. // TestSRVPriorityHandling tests nginx SRV priority handling as per nginx.org documentation
  334. func TestSRVPriorityHandling(t *testing.T) {
  335. // Create mock DNS server
  336. mockDNS := NewMockDNSServer()
  337. // Add SRV records with different priorities to test primary/backup server logic
  338. // Priority 5 (highest priority / primary servers)
  339. mockDNS.AddSRVRecord("_http._tcp.app.example.com", 5, 100, 8080, "primary1.app.example.com")
  340. mockDNS.AddSRVRecord("_http._tcp.app.example.com", 5, 50, 8080, "primary2.app.example.com")
  341. // Priority 10 (backup servers)
  342. mockDNS.AddSRVRecord("_http._tcp.app.example.com", 10, 80, 8080, "backup1.app.example.com")
  343. // Priority 15 (lower priority backup servers)
  344. mockDNS.AddSRVRecord("_http._tcp.app.example.com", 15, 200, 8080, "backup2.app.example.com")
  345. // Add A records
  346. mockDNS.AddARecord("primary1.app.example.com", "10.0.1.1")
  347. mockDNS.AddARecord("primary2.app.example.com", "10.0.1.2")
  348. mockDNS.AddARecord("backup1.app.example.com", "10.0.2.1")
  349. mockDNS.AddARecord("backup2.app.example.com", "10.0.3.1")
  350. t.Run("SRV priority handling", func(t *testing.T) {
  351. mockResolver := &MockResolver{mockServer: mockDNS}
  352. // Test SRV lookup
  353. _, srvRecords, err := mockResolver.LookupSRV(context.Background(), "", "", "_http._tcp.app.example.com")
  354. if err != nil {
  355. t.Fatalf("SRV lookup failed: %v", err)
  356. }
  357. if len(srvRecords) != 4 {
  358. t.Errorf("Expected 4 SRV records, got %d", len(srvRecords))
  359. }
  360. // According to nginx.org: "Highest-priority SRV records (records with the same lowest-number priority value)
  361. // are resolved as primary servers, the rest of SRV records are resolved as backup servers"
  362. expectedOrder := []struct {
  363. priority uint16
  364. weight uint16
  365. target string
  366. serverType string
  367. }{
  368. {5, 100, "primary1.app.example.com", "primary"}, // Highest priority (lowest number)
  369. {5, 50, "primary2.app.example.com", "primary"}, // Same priority, lower weight
  370. {10, 80, "backup1.app.example.com", "backup"}, // Lower priority (backup)
  371. {15, 200, "backup2.app.example.com", "backup"}, // Lowest priority (backup)
  372. }
  373. for i, srv := range srvRecords {
  374. expected := expectedOrder[i]
  375. if srv.Priority != expected.priority {
  376. t.Errorf("Record %d: expected priority %d, got %d", i, expected.priority, srv.Priority)
  377. }
  378. if srv.Weight != expected.weight {
  379. t.Errorf("Record %d: expected weight %d, got %d", i, expected.weight, srv.Weight)
  380. }
  381. if srv.Target != expected.target {
  382. t.Errorf("Record %d: expected target %s, got %s", i, expected.target, srv.Target)
  383. }
  384. // Log the server type for documentation
  385. t.Logf("Record %d: Priority %d, Weight %d, Target %s (%s server)",
  386. i, srv.Priority, srv.Weight, srv.Target, expected.serverType)
  387. }
  388. // Verify primary servers come first (lowest priority numbers)
  389. primaryCount := 0
  390. for _, srv := range srvRecords {
  391. if srv.Priority == 5 { // Primary servers have priority 5
  392. primaryCount++
  393. } else {
  394. break // Once we hit a non-primary, all following should be backups
  395. }
  396. }
  397. if primaryCount != 2 {
  398. t.Errorf("Expected 2 primary servers at the beginning, got %d", primaryCount)
  399. }
  400. })
  401. }
  402. // TestARecordFallback tests A record fallback when SRV lookup fails
  403. func TestARecordFallback(t *testing.T) {
  404. mockDNS := NewMockDNSServer()
  405. // Only add A record, no SRV record
  406. mockDNS.AddARecord("_http._tcp.backend.example.com", "192.168.1.100")
  407. t.Run("A record fallback", func(t *testing.T) {
  408. mockResolver := &MockResolver{mockServer: mockDNS}
  409. // SRV lookup should fail
  410. _, srvRecords, err := mockResolver.LookupSRV(context.Background(), "", "", "_http._tcp.backend.example.com")
  411. if err == nil {
  412. t.Error("Expected SRV lookup to fail")
  413. }
  414. if len(srvRecords) != 0 {
  415. t.Errorf("Expected 0 SRV records, got %d", len(srvRecords))
  416. }
  417. // A record lookup should succeed
  418. ips, err := mockResolver.LookupIPAddr(context.Background(), "_http._tcp.backend.example.com")
  419. if err != nil {
  420. t.Fatalf("A record lookup failed: %v", err)
  421. }
  422. if len(ips) != 1 {
  423. t.Errorf("Expected 1 IP, got %d", len(ips))
  424. }
  425. expectedIP := "192.168.1.100"
  426. if ips[0].IP.String() != expectedIP {
  427. t.Errorf("Expected IP %s, got %s", expectedIP, ips[0].IP.String())
  428. }
  429. })
  430. }
  431. // TestComplexNginxScenarios tests more complex real-world nginx scenarios
  432. func TestComplexNginxScenarios(t *testing.T) {
  433. tests := []struct {
  434. name string
  435. nginxLine string
  436. expectedSRV string
  437. scenario string
  438. }{
  439. {
  440. name: "Load balancer with HTTP service",
  441. nginxLine: "api.microservices.local service=http resolve",
  442. expectedSRV: "_http._tcp.api.microservices.local",
  443. scenario: "Microservices API load balancing",
  444. },
  445. {
  446. name: "Database connection",
  447. nginxLine: "db.cluster.local service=mysql resolve",
  448. expectedSRV: "_mysql._tcp.db.cluster.local",
  449. scenario: "Database cluster connection",
  450. },
  451. {
  452. name: "WebSocket service",
  453. nginxLine: "chat.app.local service=ws resolve",
  454. expectedSRV: "_ws._tcp.chat.app.local",
  455. scenario: "WebSocket service discovery",
  456. },
  457. {
  458. name: "Custom protocol with dots",
  459. nginxLine: "service.consul service=_grpc._tcp resolve",
  460. expectedSRV: "_grpc._tcp.service.consul",
  461. scenario: "gRPC service via Consul",
  462. },
  463. {
  464. name: "Multi-level service hierarchy",
  465. nginxLine: "consul.local service=api.v1.production resolve",
  466. expectedSRV: "api.v1.production.consul.local",
  467. scenario: "Multi-level service naming",
  468. },
  469. {
  470. name: "Kubernetes style service",
  471. nginxLine: "cluster.local service=_http._tcp.nginx.default resolve",
  472. expectedSRV: "_http._tcp.nginx.default.cluster.local",
  473. scenario: "Kubernetes service discovery",
  474. },
  475. }
  476. resolver := NewDynamicResolver("127.0.0.1:8600")
  477. for _, tt := range tests {
  478. t.Run(tt.name, func(t *testing.T) {
  479. serviceInfo, err := resolver.parseServiceURL(tt.nginxLine)
  480. if err != nil {
  481. t.Fatalf("Failed to parse nginx line: %v", err)
  482. }
  483. result := resolver.constructSRVDomain(serviceInfo)
  484. if result != tt.expectedSRV {
  485. t.Errorf("Scenario '%s' failed: expected %s, got %s",
  486. tt.scenario, tt.expectedSRV, result)
  487. }
  488. })
  489. }
  490. }
  491. // TestBackwardCompatibility tests backward compatibility with old format
  492. func TestBackwardCompatibility(t *testing.T) {
  493. tests := []struct {
  494. name string
  495. input string
  496. expected string
  497. }{
  498. {
  499. name: "New nginx format should work",
  500. input: "backend.example.com service=http resolve",
  501. expected: "http",
  502. },
  503. {
  504. name: "New nginx format with dots",
  505. input: "example.com service=_http._tcp resolve",
  506. expected: "_http._tcp",
  507. },
  508. {
  509. name: "Old consul format should still work as fallback",
  510. input: "test-service.service.consul",
  511. expected: "test-service",
  512. },
  513. {
  514. name: "Invalid format should return empty",
  515. input: "invalid format without proper structure",
  516. expected: "",
  517. },
  518. }
  519. resolver := NewDynamicResolver("127.0.0.1:8600")
  520. for _, tt := range tests {
  521. t.Run(tt.name, func(t *testing.T) {
  522. result := resolver.extractServiceName(tt.input)
  523. if result != tt.expected {
  524. t.Errorf("Expected %s, got %s", tt.expected, result)
  525. }
  526. })
  527. }
  528. }
  529. // TestDynamicTargetsFunction tests the TestDynamicTargets function
  530. func TestDynamicTargetsFunction(t *testing.T) {
  531. t.Run("Valid dynamic targets", func(t *testing.T) {
  532. targets := []ProxyTarget{
  533. {
  534. Host: "service.consul",
  535. Port: "dynamic",
  536. Type: "upstream",
  537. Resolver: "127.0.0.1:8600",
  538. IsConsul: true,
  539. ServiceURL: "backend.example.com service=http resolve",
  540. },
  541. }
  542. results := TestDynamicTargets(targets)
  543. if len(results) != 1 {
  544. t.Errorf("Expected 1 result, got %d", len(results))
  545. }
  546. key := "service.consul:dynamic"
  547. if _, found := results[key]; !found {
  548. t.Errorf("Expected result for key %s not found", key)
  549. }
  550. })
  551. t.Run("Target without resolver should be offline", func(t *testing.T) {
  552. targets := []ProxyTarget{
  553. {
  554. Host: "service.consul",
  555. Port: "dynamic",
  556. Type: "upstream",
  557. IsConsul: true,
  558. ServiceURL: "backend.example.com service=http resolve",
  559. // No resolver specified
  560. },
  561. }
  562. results := TestDynamicTargets(targets)
  563. key := "service.consul:dynamic"
  564. if status, found := results[key]; found {
  565. if status.Online {
  566. t.Error("Expected target without resolver to be offline")
  567. }
  568. if status.Latency != 0 {
  569. t.Errorf("Expected latency 0 for offline target, got %.2f", status.Latency)
  570. }
  571. } else {
  572. t.Errorf("Expected result for key %s", key)
  573. }
  574. })
  575. }
  576. // TestIntegrationWithProxyParser tests integration with the proxy parser
  577. func TestIntegrationWithProxyParser(t *testing.T) {
  578. config := `upstream web-backend {
  579. zone upstream_web 128k;
  580. resolver 127.0.0.1:8600 valid=5s;
  581. resolver_timeout 2s;
  582. server backend.example.com service=http resolve;
  583. }
  584. server {
  585. listen 80;
  586. server_name example.com;
  587. location / {
  588. proxy_pass http://web-backend;
  589. }
  590. }`
  591. targets := ParseProxyTargetsFromRawContent(config)
  592. // Should find the dynamic DNS target
  593. found := false
  594. for _, target := range targets {
  595. if target.IsConsul && strings.Contains(target.ServiceURL, "service=http") {
  596. found = true
  597. // Verify the target is correctly parsed
  598. if target.Resolver != "127.0.0.1:8600" {
  599. t.Errorf("Expected resolver 127.0.0.1:8600, got %s", target.Resolver)
  600. }
  601. if target.ServiceURL != "backend.example.com service=http resolve" {
  602. t.Errorf("Expected service URL 'backend.example.com service=http resolve', got %s", target.ServiceURL)
  603. }
  604. break
  605. }
  606. }
  607. if !found {
  608. t.Error("Dynamic DNS target not found in parsed config")
  609. }
  610. }