|
@@ -0,0 +1,381 @@
|
|
|
+package upstream
|
|
|
+
|
|
|
+import (
|
|
|
+ "testing"
|
|
|
+)
|
|
|
+
|
|
|
+func TestParseProxyTargetsFromRawContent(t *testing.T) {
|
|
|
+ config := `map $http_upgrade $connection_upgrade {
|
|
|
+ default upgrade;
|
|
|
+ '' close;
|
|
|
+}
|
|
|
+upstream api-1 {
|
|
|
+ server 127.0.0.1:9000;
|
|
|
+ server 127.0.0.1:443;
|
|
|
+}
|
|
|
+upstream api-2 {
|
|
|
+ server 127.0.0.1:9003;
|
|
|
+ server 127.0.0.1:9005;
|
|
|
+}
|
|
|
+server {
|
|
|
+ listen 80;
|
|
|
+ listen [::]:80;
|
|
|
+ server_name test.jackyu.cn;
|
|
|
+ location / {
|
|
|
+ # First attempt to serve request as file, then
|
|
|
+ # as directory, then fall back to displaying a 404.
|
|
|
+ index index.html;
|
|
|
+ try_files $uri $uri/ /index.html;
|
|
|
+ }
|
|
|
+ location /admin {
|
|
|
+ index admin.html;
|
|
|
+ try_files $uri $uri/ /admin.html;
|
|
|
+ }
|
|
|
+ location /user {
|
|
|
+ index user.html;
|
|
|
+ try_files $uri $uri/ /user.html;
|
|
|
+ }
|
|
|
+ location /api/ {
|
|
|
+ proxy_http_version 1.1;
|
|
|
+ proxy_set_header Upgrade $http_upgrade;
|
|
|
+ proxy_set_header Connection $connection_upgrade;
|
|
|
+ proxy_pass http://api-1/;
|
|
|
+ proxy_redirect off;
|
|
|
+ proxy_set_header Host $host;
|
|
|
+ proxy_set_header X-Real-IP $remote_addr;
|
|
|
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
|
+ proxy_set_header X-Forwarded-Proto $scheme;
|
|
|
+ client_max_body_size 1000m;
|
|
|
+ }
|
|
|
+}
|
|
|
+server {
|
|
|
+ listen 443 ssl;
|
|
|
+ listen [::]:443 ssl;
|
|
|
+ server_name test.jackyu.cn;
|
|
|
+ ssl_certificate /etc/nginx/ssl/test.jackyu.cn_P256/fullchain.cer;
|
|
|
+ ssl_certificate_key /etc/nginx/ssl/test.jackyu.cn_P256/private.key;
|
|
|
+ root /var/www/ibeta/html;
|
|
|
+ index index.html;
|
|
|
+ http2 on;
|
|
|
+ access_log /var/log/nginx/test.jackyu.cn.log main;
|
|
|
+ location / {
|
|
|
+ # First attempt to serve request as file, then
|
|
|
+ # as directory, then fall back to displaying a 404.
|
|
|
+ index index.html;
|
|
|
+ try_files $uri $uri/ /index.html;
|
|
|
+ }
|
|
|
+ location /admin {
|
|
|
+ index admin.html;
|
|
|
+ try_files $uri $uri/ /admin.html;
|
|
|
+ }
|
|
|
+ location /user {
|
|
|
+ index user.html;
|
|
|
+ try_files $uri $uri/ /user.html;
|
|
|
+ }
|
|
|
+ location /api/ {
|
|
|
+ proxy_http_version 1.1;
|
|
|
+ proxy_set_header Upgrade $http_upgrade;
|
|
|
+ proxy_set_header Connection $connection_upgrade;
|
|
|
+ proxy_pass http://api-1/;
|
|
|
+ proxy_redirect off;
|
|
|
+ proxy_set_header Host $host;
|
|
|
+ proxy_set_header X-Real-IP $remote_addr;
|
|
|
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
|
+ proxy_set_header X-Forwarded-Proto $scheme;
|
|
|
+ client_max_body_size 100m;
|
|
|
+ }
|
|
|
+}`
|
|
|
+
|
|
|
+ targets := ParseProxyTargetsFromRawContent(config)
|
|
|
+
|
|
|
+ // Expected targets: 4 upstream servers (2 from api-1, 2 from api-2)
|
|
|
+ // proxy_pass http://api-1/ should be ignored since it references an upstream
|
|
|
+ expectedTargets := []ProxyTarget{
|
|
|
+ {Host: "127.0.0.1", Port: "9000", Type: "upstream"},
|
|
|
+ {Host: "127.0.0.1", Port: "443", Type: "upstream"},
|
|
|
+ {Host: "127.0.0.1", Port: "9003", Type: "upstream"},
|
|
|
+ {Host: "127.0.0.1", Port: "9005", Type: "upstream"},
|
|
|
+ }
|
|
|
+
|
|
|
+ if len(targets) != len(expectedTargets) {
|
|
|
+ t.Errorf("Expected %d targets, got %d", len(expectedTargets), len(targets))
|
|
|
+ for i, target := range targets {
|
|
|
+ t.Logf("Target %d: %+v", i, target)
|
|
|
+ }
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // Create a map for easier comparison
|
|
|
+ targetMap := make(map[string]ProxyTarget)
|
|
|
+ for _, target := range targets {
|
|
|
+ key := target.Host + ":" + target.Port + ":" + target.Type
|
|
|
+ targetMap[key] = target
|
|
|
+ }
|
|
|
+
|
|
|
+ for _, expected := range expectedTargets {
|
|
|
+ key := expected.Host + ":" + expected.Port + ":" + expected.Type
|
|
|
+ if _, found := targetMap[key]; !found {
|
|
|
+ t.Errorf("Expected target not found: %+v", expected)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func TestIsUpstreamReference(t *testing.T) {
|
|
|
+ upstreamNames := map[string]bool{
|
|
|
+ "api-1": true,
|
|
|
+ "api-2": true,
|
|
|
+ "backend": true,
|
|
|
+ }
|
|
|
+
|
|
|
+ tests := []struct {
|
|
|
+ proxyPass string
|
|
|
+ expected bool
|
|
|
+ }{
|
|
|
+ {"http://api-1/", true},
|
|
|
+ {"http://api-1", true},
|
|
|
+ {"https://api-2/path", true},
|
|
|
+ {"http://backend", true},
|
|
|
+ {"http://127.0.0.1:8080", false},
|
|
|
+ {"https://example.com", false},
|
|
|
+ {"http://unknown-upstream", false},
|
|
|
+ }
|
|
|
+
|
|
|
+ for _, test := range tests {
|
|
|
+ result := isUpstreamReference(test.proxyPass, upstreamNames)
|
|
|
+ if result != test.expected {
|
|
|
+ t.Errorf("isUpstreamReference(%q) = %v, expected %v", test.proxyPass, result, test.expected)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func TestParseProxyTargetsWithDirectProxyPass(t *testing.T) {
|
|
|
+ config := `upstream api-1 {
|
|
|
+ server 127.0.0.1:9000;
|
|
|
+ server 127.0.0.1:443;
|
|
|
+}
|
|
|
+server {
|
|
|
+ listen 80;
|
|
|
+ server_name test.jackyu.cn;
|
|
|
+ location /api/ {
|
|
|
+ proxy_pass http://api-1/;
|
|
|
+ }
|
|
|
+ location /external/ {
|
|
|
+ proxy_pass http://external.example.com:8080/;
|
|
|
+ }
|
|
|
+ location /another/ {
|
|
|
+ proxy_pass https://another.example.com/;
|
|
|
+ }
|
|
|
+}`
|
|
|
+
|
|
|
+ targets := ParseProxyTargetsFromRawContent(config)
|
|
|
+
|
|
|
+ // Expected targets:
|
|
|
+ // - 2 upstream servers from api-1
|
|
|
+ // - 2 direct proxy_pass targets (external.example.com:8080, another.example.com:443)
|
|
|
+ // - proxy_pass http://api-1/ should be ignored since it references an upstream
|
|
|
+ expectedTargets := []ProxyTarget{
|
|
|
+ {Host: "127.0.0.1", Port: "9000", Type: "upstream"},
|
|
|
+ {Host: "127.0.0.1", Port: "443", Type: "upstream"},
|
|
|
+ {Host: "external.example.com", Port: "8080", Type: "proxy_pass"},
|
|
|
+ {Host: "another.example.com", Port: "443", Type: "proxy_pass"},
|
|
|
+ }
|
|
|
+
|
|
|
+ if len(targets) != len(expectedTargets) {
|
|
|
+ t.Errorf("Expected %d targets, got %d", len(expectedTargets), len(targets))
|
|
|
+ for i, target := range targets {
|
|
|
+ t.Logf("Target %d: %+v", i, target)
|
|
|
+ }
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // Create a map for easier comparison
|
|
|
+ targetMap := make(map[string]ProxyTarget)
|
|
|
+ for _, target := range targets {
|
|
|
+ key := target.Host + ":" + target.Port + ":" + target.Type
|
|
|
+ targetMap[key] = target
|
|
|
+ }
|
|
|
+
|
|
|
+ for _, expected := range expectedTargets {
|
|
|
+ key := expected.Host + ":" + expected.Port + ":" + expected.Type
|
|
|
+ if _, found := targetMap[key]; !found {
|
|
|
+ t.Errorf("Expected target not found: %+v", expected)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func TestParseProxyTargetsFromStreamConfig(t *testing.T) {
|
|
|
+ config := `upstream backend {
|
|
|
+ server 127.0.0.1:9000;
|
|
|
+ server 127.0.0.1:9001;
|
|
|
+}
|
|
|
+
|
|
|
+server {
|
|
|
+ listen 12345;
|
|
|
+ proxy_pass backend;
|
|
|
+}
|
|
|
+
|
|
|
+server {
|
|
|
+ listen 12346;
|
|
|
+ proxy_pass 192.168.1.100:8080;
|
|
|
+}
|
|
|
+
|
|
|
+server {
|
|
|
+ listen 12347;
|
|
|
+ proxy_pass example.com:3306;
|
|
|
+}`
|
|
|
+
|
|
|
+ targets := ParseProxyTargetsFromRawContent(config)
|
|
|
+
|
|
|
+ // Expected targets:
|
|
|
+ // - 2 upstream servers from backend
|
|
|
+ // - 2 direct proxy_pass targets (192.168.1.100:8080, example.com:3306)
|
|
|
+ // - proxy_pass backend should be ignored since it references an upstream
|
|
|
+ expectedTargets := []ProxyTarget{
|
|
|
+ {Host: "127.0.0.1", Port: "9000", Type: "upstream"},
|
|
|
+ {Host: "127.0.0.1", Port: "9001", Type: "upstream"},
|
|
|
+ {Host: "192.168.1.100", Port: "8080", Type: "proxy_pass"},
|
|
|
+ {Host: "example.com", Port: "3306", Type: "proxy_pass"},
|
|
|
+ }
|
|
|
+
|
|
|
+ if len(targets) != len(expectedTargets) {
|
|
|
+ t.Errorf("Expected %d targets, got %d", len(expectedTargets), len(targets))
|
|
|
+ for i, target := range targets {
|
|
|
+ t.Logf("Target %d: %+v", i, target)
|
|
|
+ }
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // Create a map for easier comparison
|
|
|
+ targetMap := make(map[string]ProxyTarget)
|
|
|
+ for _, target := range targets {
|
|
|
+ key := target.Host + ":" + target.Port + ":" + target.Type
|
|
|
+ targetMap[key] = target
|
|
|
+ }
|
|
|
+
|
|
|
+ for _, expected := range expectedTargets {
|
|
|
+ key := expected.Host + ":" + expected.Port + ":" + expected.Type
|
|
|
+ if _, found := targetMap[key]; !found {
|
|
|
+ t.Errorf("Expected target not found: %+v", expected)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func TestParseProxyTargetsFromMixedConfig(t *testing.T) {
|
|
|
+ config := `upstream web_backend {
|
|
|
+ server web1.example.com:80;
|
|
|
+ server web2.example.com:80;
|
|
|
+}
|
|
|
+
|
|
|
+upstream stream_backend {
|
|
|
+ server stream1.example.com:12345;
|
|
|
+ server stream2.example.com:12345;
|
|
|
+}
|
|
|
+
|
|
|
+# HTTP server block
|
|
|
+server {
|
|
|
+ listen 80;
|
|
|
+ server_name example.com;
|
|
|
+ location / {
|
|
|
+ proxy_pass http://web_backend/;
|
|
|
+ }
|
|
|
+ location /api {
|
|
|
+ proxy_pass http://api.example.com:8080/;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+# Stream server blocks
|
|
|
+server {
|
|
|
+ listen 12345;
|
|
|
+ proxy_pass stream_backend;
|
|
|
+}
|
|
|
+
|
|
|
+server {
|
|
|
+ listen 3306;
|
|
|
+ proxy_pass mysql.example.com:3306;
|
|
|
+}`
|
|
|
+
|
|
|
+ targets := ParseProxyTargetsFromRawContent(config)
|
|
|
+
|
|
|
+ // Expected targets:
|
|
|
+ // - 2 upstream servers from web_backend
|
|
|
+ // - 2 upstream servers from stream_backend
|
|
|
+ // - 1 direct HTTP proxy_pass (api.example.com:8080)
|
|
|
+ // - 1 direct stream proxy_pass (mysql.example.com:3306)
|
|
|
+ // - proxy_pass http://web_backend/ and proxy_pass stream_backend should be ignored
|
|
|
+ expectedTargets := []ProxyTarget{
|
|
|
+ {Host: "web1.example.com", Port: "80", Type: "upstream"},
|
|
|
+ {Host: "web2.example.com", Port: "80", Type: "upstream"},
|
|
|
+ {Host: "stream1.example.com", Port: "12345", Type: "upstream"},
|
|
|
+ {Host: "stream2.example.com", Port: "12345", Type: "upstream"},
|
|
|
+ {Host: "api.example.com", Port: "8080", Type: "proxy_pass"},
|
|
|
+ {Host: "mysql.example.com", Port: "3306", Type: "proxy_pass"},
|
|
|
+ }
|
|
|
+
|
|
|
+ if len(targets) != len(expectedTargets) {
|
|
|
+ t.Errorf("Expected %d targets, got %d", len(expectedTargets), len(targets))
|
|
|
+ for i, target := range targets {
|
|
|
+ t.Logf("Target %d: %+v", i, target)
|
|
|
+ }
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // Create a map for easier comparison
|
|
|
+ targetMap := make(map[string]ProxyTarget)
|
|
|
+ for _, target := range targets {
|
|
|
+ key := target.Host + ":" + target.Port + ":" + target.Type
|
|
|
+ targetMap[key] = target
|
|
|
+ }
|
|
|
+
|
|
|
+ for _, expected := range expectedTargets {
|
|
|
+ key := expected.Host + ":" + expected.Port + ":" + expected.Type
|
|
|
+ if _, found := targetMap[key]; !found {
|
|
|
+ t.Errorf("Expected target not found: %+v", expected)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func TestParseProxyTargetsFromUserConfig(t *testing.T) {
|
|
|
+ config := `upstream my-tcp {
|
|
|
+ server 127.0.0.1:9000;
|
|
|
+}
|
|
|
+server {
|
|
|
+ listen 1234-1236;
|
|
|
+ resolver 8.8.8.8 valid=1s;
|
|
|
+ proxy_pass example.com:$server_port;
|
|
|
+}`
|
|
|
+
|
|
|
+ targets := ParseProxyTargetsFromRawContent(config)
|
|
|
+
|
|
|
+ // Print actual results for debugging
|
|
|
+ t.Logf("Found %d targets:", len(targets))
|
|
|
+ for i, target := range targets {
|
|
|
+ t.Logf("Target %d: Host=%s, Port=%s, Type=%s", i+1, target.Host, target.Port, target.Type)
|
|
|
+ }
|
|
|
+
|
|
|
+ // Expected targets:
|
|
|
+ // - 1 upstream server from my-tcp
|
|
|
+ // - 1 proxy_pass target (example.com with variable port should still be parsed)
|
|
|
+ expectedTargets := []ProxyTarget{
|
|
|
+ {Host: "127.0.0.1", Port: "9000", Type: "upstream"},
|
|
|
+ {Host: "example.com", Port: "$server_port", Type: "proxy_pass"},
|
|
|
+ }
|
|
|
+
|
|
|
+ if len(targets) != len(expectedTargets) {
|
|
|
+ t.Errorf("Expected %d targets, got %d", len(expectedTargets), len(targets))
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // Create a map for easier comparison
|
|
|
+ targetMap := make(map[string]ProxyTarget)
|
|
|
+ for _, target := range targets {
|
|
|
+ key := target.Host + ":" + target.Port + ":" + target.Type
|
|
|
+ targetMap[key] = target
|
|
|
+ }
|
|
|
+
|
|
|
+ for _, expected := range expectedTargets {
|
|
|
+ key := expected.Host + ":" + expected.Port + ":" + expected.Type
|
|
|
+ if _, found := targetMap[key]; !found {
|
|
|
+ t.Errorf("Expected target not found: %+v", expected)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|