dnsmasq (6 new, 30.8% → 32%): - Empty instances, multiple instances, nil config, configured IP - No leaked empty entries (address=//, local=//) - Empty fields don't produce broken entries Also fixed a bug: empty Domain/InternalDomain fields in commented-out entries (no LB IP case) now guarded against producing # local=// and # address=// directives. haproxy (7 new): - No HTTPS frontend with no routes - HTTP-only and L4-only route scenarios - CertsDir customization and default - Custom TCP route ports - Health check directive in L7 backends services (8 new, 72.7% → 75.8%): - Not-found errors for Get, Deregister, Update - DeregisterBySource partial match (source AND backend) - Overwrite preserves file count - Empty dir and non-YAML file handling Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
571 lines
18 KiB
Go
571 lines
18 KiB
Go
package haproxy
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestNewManager_DefaultPath(t *testing.T) {
|
|
m := NewManager("")
|
|
if m.GetConfigPath() != "/etc/haproxy/haproxy.cfg" {
|
|
t.Errorf("got %q, want default path", m.GetConfigPath())
|
|
}
|
|
}
|
|
|
|
func TestNewManager_CustomPath(t *testing.T) {
|
|
m := NewManager("/tmp/haproxy.cfg")
|
|
if m.GetConfigPath() != "/tmp/haproxy.cfg" {
|
|
t.Errorf("got %q, want /tmp/haproxy.cfg", m.GetConfigPath())
|
|
}
|
|
}
|
|
|
|
func TestSanitizeName(t *testing.T) {
|
|
cases := []struct {
|
|
in string
|
|
want string
|
|
}{
|
|
{"payne-cloud", "payne_cloud"},
|
|
{"cloud.payne.io", "cloud_payne_io"},
|
|
{"civil_ssh", "civil_ssh"},
|
|
{"test cloud 1", "test_cloud_1"},
|
|
{"abc123", "abc123"},
|
|
}
|
|
for _, c := range cases {
|
|
if got := sanitizeName(c.in); got != c.want {
|
|
t.Errorf("sanitizeName(%q) = %q, want %q", c.in, got, c.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestGetListenPorts_BasePorts(t *testing.T) {
|
|
m := NewManager("")
|
|
ports := m.GetListenPorts(nil, nil)
|
|
want := map[int]bool{80: true, 443: true, 8404: true}
|
|
for _, p := range ports {
|
|
delete(want, p)
|
|
}
|
|
if len(want) > 0 {
|
|
t.Errorf("missing base ports: %v", want)
|
|
}
|
|
}
|
|
|
|
func TestGetListenPorts_IncludesCustom(t *testing.T) {
|
|
m := NewManager("")
|
|
custom := []CustomRoute{{Name: "ssh", Port: 2222, Backend: "192.168.1.10:22"}}
|
|
ports := m.GetListenPorts(nil, custom)
|
|
found := false
|
|
for _, p := range ports {
|
|
if p == 2222 {
|
|
found = true
|
|
}
|
|
}
|
|
if !found {
|
|
t.Errorf("custom port 2222 not in listen ports: %v", ports)
|
|
}
|
|
}
|
|
|
|
func TestGenerate_AlwaysIncludesBoilerplate(t *testing.T) {
|
|
m := NewManager("")
|
|
out := m.Generate(nil, nil)
|
|
|
|
for _, want := range []string{
|
|
"global\n",
|
|
"defaults\n",
|
|
"frontend stats\n",
|
|
"frontend http_in\n",
|
|
"redirect scheme https code 301",
|
|
"bind *:8404",
|
|
} {
|
|
if !strings.Contains(out, want) {
|
|
t.Errorf("expected %q in output, got:\n%s", want, out)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestGenerate_NoInstances_NoHTTPSFrontend(t *testing.T) {
|
|
m := NewManager("")
|
|
out := m.Generate(nil, nil)
|
|
if strings.Contains(out, "frontend https_in") {
|
|
t.Errorf("https_in frontend should be absent when no instances configured")
|
|
}
|
|
}
|
|
|
|
func TestGenerate_WithInstance_SNIACLs(t *testing.T) {
|
|
m := NewManager("")
|
|
instances := []InstanceRoute{
|
|
{Name: "payne-cloud", Domain: "cloud.payne.io", BackendIP: "192.168.8.20", Subdomains: true},
|
|
}
|
|
out := m.Generate(instances, nil)
|
|
|
|
// With subdomains:true, both ACL lines for OR-matching
|
|
if !strings.Contains(out, "req_ssl_sni -m end .cloud.payne.io") {
|
|
t.Errorf("expected subdomain SNI ACL, got:\n%s", out)
|
|
}
|
|
if !strings.Contains(out, "req_ssl_sni -m str cloud.payne.io") {
|
|
t.Errorf("expected exact SNI ACL, got:\n%s", out)
|
|
}
|
|
if !strings.Contains(out, "use_backend be_https_payne_cloud if is_payne_cloud") {
|
|
t.Errorf("expected use_backend directive, got:\n%s", out)
|
|
}
|
|
}
|
|
|
|
func TestGenerate_WithInstance_Backend(t *testing.T) {
|
|
m := NewManager("")
|
|
instances := []InstanceRoute{
|
|
{Name: "payne-cloud", Domain: "cloud.payne.io", BackendIP: "192.168.8.20"},
|
|
}
|
|
out := m.Generate(instances, nil)
|
|
|
|
if !strings.Contains(out, "backend be_https_payne_cloud") {
|
|
t.Errorf("expected backend block, got:\n%s", out)
|
|
}
|
|
if !strings.Contains(out, "server s0 192.168.8.20:443 check") {
|
|
t.Errorf("expected server line with LB IP, got:\n%s", out)
|
|
}
|
|
}
|
|
|
|
func TestGenerate_MultipleInstances(t *testing.T) {
|
|
m := NewManager("")
|
|
instances := []InstanceRoute{
|
|
{Name: "payne-cloud", Domain: "cloud.payne.io", BackendIP: "192.168.8.20"},
|
|
{Name: "test-cloud", Domain: "cloud2.payne.io", BackendIP: "192.168.8.30"},
|
|
}
|
|
out := m.Generate(instances, nil)
|
|
|
|
if !strings.Contains(out, "be_https_payne_cloud") {
|
|
t.Errorf("expected payne-cloud backend, got:\n%s", out)
|
|
}
|
|
if !strings.Contains(out, "be_https_test_cloud") {
|
|
t.Errorf("expected test-cloud backend, got:\n%s", out)
|
|
}
|
|
if !strings.Contains(out, "192.168.8.20:443") {
|
|
t.Errorf("expected payne-cloud IP, got:\n%s", out)
|
|
}
|
|
if !strings.Contains(out, "192.168.8.30:443") {
|
|
t.Errorf("expected test-cloud IP, got:\n%s", out)
|
|
}
|
|
}
|
|
|
|
func TestGenerate_WithSubdomains(t *testing.T) {
|
|
m := NewManager("")
|
|
instances := []InstanceRoute{
|
|
{Name: "payne-cloud", Domain: "cloud.payne.io", BackendIP: "192.168.8.20", Subdomains: true},
|
|
{Name: "payne-io", Domain: "payne.io", BackendIP: "192.168.8.20", Subdomains: false},
|
|
}
|
|
out := m.Generate(instances, nil)
|
|
|
|
// cloud.payne.io with subdomains:true should have wildcard ACL
|
|
if !strings.Contains(out, "req_ssl_sni -m end .cloud.payne.io") {
|
|
t.Errorf("expected wildcard ACL for cloud.payne.io, got:\n%s", out)
|
|
}
|
|
// payne.io with subdomains:false should have exact match only
|
|
if !strings.Contains(out, "req_ssl_sni -m str payne.io") {
|
|
t.Errorf("expected exact ACL for payne.io, got:\n%s", out)
|
|
}
|
|
// payne.io should NOT have wildcard match
|
|
if strings.Contains(out, "req_ssl_sni -m end .payne.io") {
|
|
t.Errorf("unexpected wildcard ACL for payne.io (subdomains:false), got:\n%s", out)
|
|
}
|
|
}
|
|
|
|
func TestGenerate_ACLOrdering(t *testing.T) {
|
|
m := NewManager("")
|
|
instances := []InstanceRoute{
|
|
{Name: "payne-cloud", Domain: "cloud.payne.io", BackendIP: "192.168.8.240", Subdomains: true},
|
|
}
|
|
httpRoutes := []HTTPRoute{
|
|
{Name: "wild-central", Domain: "central.payne.io", Backend: "127.0.0.1:15055"},
|
|
{Name: "wild-cloud", Domain: "wild-cloud.payne.io", Backend: "127.0.0.1:5055"},
|
|
}
|
|
out := m.GenerateWithOpts(instances, nil, GenerateOpts{HTTPRoutes: httpRoutes})
|
|
|
|
// L7 exact matches must appear BEFORE L4 wildcard matches
|
|
l7Pos := strings.Index(out, "is_l7_wild_central")
|
|
l4Pos := strings.Index(out, "is_payne_cloud")
|
|
|
|
if l7Pos < 0 {
|
|
t.Fatalf("expected L7 ACL for wild-central, got:\n%s", out)
|
|
}
|
|
if l4Pos < 0 {
|
|
t.Fatalf("expected L4 ACL for payne-cloud, got:\n%s", out)
|
|
}
|
|
if l7Pos > l4Pos {
|
|
t.Errorf("L7 exact match (pos %d) must come before L4 wildcard (pos %d):\n%s", l7Pos, l4Pos, out)
|
|
}
|
|
|
|
// wild-cloud.payne.io should NOT match payne-cloud's wildcard
|
|
// (it should be caught by the L7 exact match first)
|
|
if strings.Contains(out, "use_backend be_https_payne_cloud if is_l7_wild_cloud") {
|
|
t.Errorf("wild-cloud.payne.io should route to L7, not payne-cloud")
|
|
}
|
|
}
|
|
|
|
func TestGenerate_CustomRoute(t *testing.T) {
|
|
m := NewManager("")
|
|
custom := []CustomRoute{
|
|
{Name: "civil_ssh", Port: 2222, Backend: "192.168.8.10:22"},
|
|
}
|
|
out := m.Generate(nil, custom)
|
|
|
|
if !strings.Contains(out, "frontend fe_civil_ssh") {
|
|
t.Errorf("expected custom frontend, got:\n%s", out)
|
|
}
|
|
if !strings.Contains(out, "bind *:2222") {
|
|
t.Errorf("expected bind on port 2222, got:\n%s", out)
|
|
}
|
|
if !strings.Contains(out, "backend be_civil_ssh") {
|
|
t.Errorf("expected custom backend, got:\n%s", out)
|
|
}
|
|
if !strings.Contains(out, "server s0 192.168.8.10:22") {
|
|
t.Errorf("expected server line with backend address, got:\n%s", out)
|
|
}
|
|
}
|
|
|
|
// --- L7 HTTP route tests ---
|
|
|
|
func TestGenerateWithOpts_HTTPRoutes_L7Frontend(t *testing.T) {
|
|
m := NewManager("")
|
|
httpRoutes := []HTTPRoute{
|
|
{Name: "my-api", Domain: "my-api.payne.io", Backend: "192.168.8.60:9001", HealthPath: "/health"},
|
|
{Name: "dashboard", Domain: "dashboard.payne.io", Backend: "192.168.8.60:8080"},
|
|
}
|
|
|
|
out := m.GenerateWithOpts(nil, nil, GenerateOpts{
|
|
HTTPRoutes: httpRoutes,
|
|
CertsDir: "/etc/haproxy/certs/",
|
|
})
|
|
|
|
// L7 TLS termination frontend should exist
|
|
if !strings.Contains(out, "frontend l7_https") {
|
|
t.Errorf("expected L7 frontend, got:\n%s", out)
|
|
}
|
|
if !strings.Contains(out, "ssl crt /etc/haproxy/certs/") {
|
|
t.Errorf("expected certs dir in L7 bind, got:\n%s", out)
|
|
}
|
|
|
|
// Host-based ACLs
|
|
if !strings.Contains(out, "hdr(host) -i my-api.payne.io") {
|
|
t.Errorf("expected Host ACL for my-api, got:\n%s", out)
|
|
}
|
|
if !strings.Contains(out, "hdr(host) -i dashboard.payne.io") {
|
|
t.Errorf("expected Host ACL for dashboard, got:\n%s", out)
|
|
}
|
|
|
|
// L7 backends
|
|
if !strings.Contains(out, "backend be_l7_my_api") {
|
|
t.Errorf("expected L7 backend for my-api, got:\n%s", out)
|
|
}
|
|
if !strings.Contains(out, "server s0 192.168.8.60:9001 check") {
|
|
t.Errorf("expected my-api server with health check, got:\n%s", out)
|
|
}
|
|
if !strings.Contains(out, "option httpchk GET /health") {
|
|
t.Errorf("expected httpchk for my-api, got:\n%s", out)
|
|
}
|
|
|
|
// Dashboard backend (no health check)
|
|
if !strings.Contains(out, "backend be_l7_dashboard") {
|
|
t.Errorf("expected L7 backend for dashboard, got:\n%s", out)
|
|
}
|
|
if !strings.Contains(out, "server s0 192.168.8.60:8080") {
|
|
t.Errorf("expected dashboard server line, got:\n%s", out)
|
|
}
|
|
}
|
|
|
|
func TestGenerateWithOpts_MixedL4AndL7(t *testing.T) {
|
|
m := NewManager("")
|
|
instances := []InstanceRoute{
|
|
{Name: "payne-cloud", Domain: "cloud.payne.io", BackendIP: "192.168.8.100"},
|
|
}
|
|
httpRoutes := []HTTPRoute{
|
|
{Name: "my-api", Domain: "my-api.payne.io", Backend: "192.168.8.60:9001"},
|
|
}
|
|
|
|
out := m.GenerateWithOpts(instances, nil, GenerateOpts{
|
|
HTTPRoutes: httpRoutes,
|
|
})
|
|
|
|
// Both L4 SNI route and L7 route should be in the same config
|
|
if !strings.Contains(out, "use_backend be_https_payne_cloud if is_payne_cloud") {
|
|
t.Errorf("expected L4 instance route, got:\n%s", out)
|
|
}
|
|
if !strings.Contains(out, "frontend l7_https") {
|
|
t.Errorf("expected L7 frontend, got:\n%s", out)
|
|
}
|
|
if !strings.Contains(out, "backend be_l7_my_api") {
|
|
t.Errorf("expected L7 backend for my-api, got:\n%s", out)
|
|
}
|
|
// L7 fallback should be present
|
|
if !strings.Contains(out, "default_backend be_l7_termination") {
|
|
t.Errorf("expected default_backend for L7 termination, got:\n%s", out)
|
|
}
|
|
}
|
|
|
|
func TestGenerateWithOpts_ACLOrdering_L7BeforeL4Wildcard(t *testing.T) {
|
|
m := NewManager("")
|
|
|
|
// Scenario: cloud.payne.io with subdomains:true is an L4 wildcard route.
|
|
// wild-cloud.payne.io is an L7 HTTP service. The L7 exact match MUST
|
|
// appear before the L4 wildcard, otherwise HAProxy matches
|
|
// wild-cloud.payne.io against *.payne.io and sends it to the wrong backend.
|
|
instances := []InstanceRoute{
|
|
{Name: "payne-io", Domain: "payne.io", BackendIP: "192.168.8.240", Subdomains: true},
|
|
}
|
|
httpRoutes := []HTTPRoute{
|
|
{Name: "wild-central", Domain: "central.payne.io", Backend: "127.0.0.1:15055"},
|
|
{Name: "wild-cloud", Domain: "wild-cloud.payne.io", Backend: "127.0.0.1:5055"},
|
|
}
|
|
out := m.GenerateWithOpts(instances, nil, GenerateOpts{HTTPRoutes: httpRoutes})
|
|
|
|
// All L7 exact matches must appear before L4 wildcard matches
|
|
l7Central := strings.Index(out, "is_l7_wild_central")
|
|
l7WildCloud := strings.Index(out, "is_l7_wild_cloud")
|
|
l4Wildcard := strings.Index(out, "req_ssl_sni -m end .payne.io")
|
|
|
|
if l7Central < 0 {
|
|
t.Fatalf("expected L7 ACL for wild-central, got:\n%s", out)
|
|
}
|
|
if l7WildCloud < 0 {
|
|
t.Fatalf("expected L7 ACL for wild-cloud, got:\n%s", out)
|
|
}
|
|
if l4Wildcard < 0 {
|
|
t.Fatalf("expected L4 wildcard ACL for payne.io, got:\n%s", out)
|
|
}
|
|
|
|
if l7Central > l4Wildcard {
|
|
t.Errorf("L7 central.payne.io (pos %d) must appear before L4 *.payne.io wildcard (pos %d)", l7Central, l4Wildcard)
|
|
}
|
|
if l7WildCloud > l4Wildcard {
|
|
t.Errorf("L7 wild-cloud.payne.io (pos %d) must appear before L4 *.payne.io wildcard (pos %d)", l7WildCloud, l4Wildcard)
|
|
}
|
|
|
|
// Verify the config order overall: L7 exact → L4 wildcard → default
|
|
defaultBackend := strings.Index(out, "default_backend be_l7_termination")
|
|
if defaultBackend < 0 {
|
|
t.Fatalf("expected default_backend for L7 termination, got:\n%s", out)
|
|
}
|
|
if l4Wildcard > defaultBackend {
|
|
t.Errorf("L4 wildcard (pos %d) must appear before default_backend (pos %d)", l4Wildcard, defaultBackend)
|
|
}
|
|
}
|
|
|
|
func TestGenerateWithOpts_SubdomainsFalse_ExactOnly(t *testing.T) {
|
|
m := NewManager("")
|
|
instances := []InstanceRoute{
|
|
{Name: "payne-io", Domain: "payne.io", BackendIP: "192.168.8.20", Subdomains: false},
|
|
}
|
|
out := m.GenerateWithOpts(instances, nil, GenerateOpts{})
|
|
|
|
// subdomains:false must produce ONLY -m str (exact), NOT -m end (wildcard)
|
|
if !strings.Contains(out, "req_ssl_sni -m str payne.io") {
|
|
t.Errorf("expected exact match ACL for payne.io, got:\n%s", out)
|
|
}
|
|
if strings.Contains(out, "req_ssl_sni -m end .payne.io") {
|
|
t.Errorf("subdomains:false must NOT produce wildcard ACL, got:\n%s", out)
|
|
}
|
|
}
|
|
|
|
func TestGenerateWithOpts_SubdomainsTrue_WildcardAndExact(t *testing.T) {
|
|
m := NewManager("")
|
|
instances := []InstanceRoute{
|
|
{Name: "cloud-payne", Domain: "cloud.payne.io", BackendIP: "192.168.8.20", Subdomains: true},
|
|
}
|
|
out := m.GenerateWithOpts(instances, nil, GenerateOpts{})
|
|
|
|
// subdomains:true must produce BOTH -m end (wildcard) AND -m str (exact)
|
|
if !strings.Contains(out, "req_ssl_sni -m end .cloud.payne.io") {
|
|
t.Errorf("subdomains:true must produce wildcard ACL (-m end), got:\n%s", out)
|
|
}
|
|
if !strings.Contains(out, "req_ssl_sni -m str cloud.payne.io") {
|
|
t.Errorf("subdomains:true must also produce exact ACL (-m str), got:\n%s", out)
|
|
}
|
|
|
|
// Both ACLs should use the same backend
|
|
if !strings.Contains(out, "use_backend be_https_cloud_payne if is_cloud_payne") {
|
|
t.Errorf("expected use_backend for cloud_payne, got:\n%s", out)
|
|
}
|
|
}
|
|
|
|
func TestGenerateWithOpts_ACLOrdering_L4ExactBeforeL4Wildcard(t *testing.T) {
|
|
m := NewManager("")
|
|
// payne.io exact (subdomains:false) must appear before cloud.payne.io wildcard (subdomains:true)
|
|
instances := []InstanceRoute{
|
|
{Name: "cloud-payne", Domain: "cloud.payne.io", BackendIP: "192.168.8.20", Subdomains: true},
|
|
{Name: "payne-io", Domain: "payne.io", BackendIP: "192.168.8.20", Subdomains: false},
|
|
}
|
|
out := m.GenerateWithOpts(instances, nil, GenerateOpts{})
|
|
|
|
exactPos := strings.Index(out, "req_ssl_sni -m str payne.io")
|
|
wildcardPos := strings.Index(out, "req_ssl_sni -m end .cloud.payne.io")
|
|
|
|
if exactPos < 0 {
|
|
t.Fatalf("expected exact match ACL for payne.io, got:\n%s", out)
|
|
}
|
|
if wildcardPos < 0 {
|
|
t.Fatalf("expected wildcard ACL for cloud.payne.io, got:\n%s", out)
|
|
}
|
|
if exactPos > wildcardPos {
|
|
t.Errorf("L4 exact match (pos %d) must come before L4 wildcard (pos %d):\n%s", exactPos, wildcardPos, out)
|
|
}
|
|
}
|
|
|
|
func TestGenerateWithOpts_BackwardCompatible(t *testing.T) {
|
|
m := NewManager("")
|
|
instances := []InstanceRoute{
|
|
{Name: "test", Domain: "test.example.com", BackendIP: "10.0.0.1"},
|
|
}
|
|
|
|
// Using old Generate function should still work
|
|
out := m.Generate(instances, nil)
|
|
if !strings.Contains(out, "be_https_test") {
|
|
t.Errorf("backward compat: expected instance backend, got:\n%s", out)
|
|
}
|
|
// No L7 frontend when no HTTP routes
|
|
if strings.Contains(out, "frontend l7_https") {
|
|
t.Errorf("backward compat: unexpected L7 frontend, got:\n%s", out)
|
|
}
|
|
}
|
|
|
|
func TestGenerateWithOpts_NoRoutes_NoHTTPSFrontend(t *testing.T) {
|
|
m := NewManager("")
|
|
|
|
// No instances, no HTTP routes
|
|
out := m.GenerateWithOpts(nil, nil, GenerateOpts{})
|
|
|
|
if strings.Contains(out, "frontend https_in") {
|
|
t.Errorf("expected no HTTPS frontend with empty instances and empty HTTP routes, got:\n%s", out)
|
|
}
|
|
if strings.Contains(out, "frontend l7_https") {
|
|
t.Errorf("expected no L7 frontend with no HTTP routes, got:\n%s", out)
|
|
}
|
|
}
|
|
|
|
func TestGenerateWithOpts_OnlyHTTPRoutes(t *testing.T) {
|
|
m := NewManager("")
|
|
|
|
httpRoutes := []HTTPRoute{
|
|
{Name: "my-api", Domain: "api.example.com", Backend: "192.168.1.10:9001"},
|
|
}
|
|
|
|
out := m.GenerateWithOpts(nil, nil, GenerateOpts{HTTPRoutes: httpRoutes})
|
|
|
|
// HTTPS frontend should be present (needed for SNI dispatch to L7)
|
|
if !strings.Contains(out, "frontend https_in") {
|
|
t.Errorf("expected HTTPS frontend when HTTP routes present, got:\n%s", out)
|
|
}
|
|
// L7 frontend should be present
|
|
if !strings.Contains(out, "frontend l7_https") {
|
|
t.Errorf("expected L7 frontend for HTTP routes, got:\n%s", out)
|
|
}
|
|
// L7 backend should be present
|
|
if !strings.Contains(out, "backend be_l7_my_api") {
|
|
t.Errorf("expected L7 backend for my-api, got:\n%s", out)
|
|
}
|
|
// No L4 instance backends
|
|
if strings.Contains(out, "be_https_") {
|
|
t.Errorf("unexpected L4 instance backend when only HTTP routes, got:\n%s", out)
|
|
}
|
|
}
|
|
|
|
func TestGenerateWithOpts_OnlyL4Routes(t *testing.T) {
|
|
m := NewManager("")
|
|
|
|
instances := []InstanceRoute{
|
|
{Name: "cloud", Domain: "cloud.example.com", BackendIP: "10.0.0.1", Subdomains: true},
|
|
}
|
|
|
|
out := m.GenerateWithOpts(instances, nil, GenerateOpts{})
|
|
|
|
// HTTPS frontend should exist for L4 SNI routing
|
|
if !strings.Contains(out, "frontend https_in") {
|
|
t.Errorf("expected HTTPS frontend for L4 routes, got:\n%s", out)
|
|
}
|
|
// L4 backend should exist
|
|
if !strings.Contains(out, "backend be_https_cloud") {
|
|
t.Errorf("expected L4 backend, got:\n%s", out)
|
|
}
|
|
// No L7 frontend when no HTTP routes
|
|
if strings.Contains(out, "frontend l7_https") {
|
|
t.Errorf("unexpected L7 frontend with only L4 routes, got:\n%s", out)
|
|
}
|
|
// No L7 termination backend
|
|
if strings.Contains(out, "be_l7_termination") {
|
|
t.Errorf("unexpected L7 termination backend with only L4 routes, got:\n%s", out)
|
|
}
|
|
}
|
|
|
|
func TestGenerateWithOpts_CertsDir(t *testing.T) {
|
|
m := NewManager("")
|
|
|
|
httpRoutes := []HTTPRoute{
|
|
{Name: "app", Domain: "app.example.com", Backend: "127.0.0.1:8080"},
|
|
}
|
|
|
|
out := m.GenerateWithOpts(nil, nil, GenerateOpts{
|
|
HTTPRoutes: httpRoutes,
|
|
CertsDir: "/opt/certs/wildcard/",
|
|
})
|
|
|
|
if !strings.Contains(out, "ssl crt /opt/certs/wildcard/") {
|
|
t.Errorf("expected custom CertsDir in L7 bind, got:\n%s", out)
|
|
}
|
|
}
|
|
|
|
func TestGenerateWithOpts_DefaultCertsDir(t *testing.T) {
|
|
m := NewManager("")
|
|
|
|
httpRoutes := []HTTPRoute{
|
|
{Name: "app", Domain: "app.example.com", Backend: "127.0.0.1:8080"},
|
|
}
|
|
|
|
// Empty CertsDir should default to /etc/haproxy/certs/
|
|
out := m.GenerateWithOpts(nil, nil, GenerateOpts{
|
|
HTTPRoutes: httpRoutes,
|
|
CertsDir: "",
|
|
})
|
|
|
|
if !strings.Contains(out, "ssl crt /etc/haproxy/certs/") {
|
|
t.Errorf("expected default CertsDir /etc/haproxy/certs/ in L7 bind, got:\n%s", out)
|
|
}
|
|
}
|
|
|
|
func TestGenerate_CustomRoutePorts(t *testing.T) {
|
|
m := NewManager("")
|
|
|
|
custom := []CustomRoute{
|
|
{Name: "ssh", Port: 2222, Backend: "192.168.1.10:22"},
|
|
{Name: "mqtt", Port: 1883, Backend: "192.168.1.20:1883"},
|
|
}
|
|
|
|
out := m.Generate(nil, custom)
|
|
|
|
if !strings.Contains(out, "bind *:2222") {
|
|
t.Errorf("expected bind on port 2222, got:\n%s", out)
|
|
}
|
|
if !strings.Contains(out, "bind *:1883") {
|
|
t.Errorf("expected bind on port 1883, got:\n%s", out)
|
|
}
|
|
if !strings.Contains(out, "server s0 192.168.1.10:22") {
|
|
t.Errorf("expected SSH backend, got:\n%s", out)
|
|
}
|
|
if !strings.Contains(out, "server s0 192.168.1.20:1883") {
|
|
t.Errorf("expected MQTT backend, got:\n%s", out)
|
|
}
|
|
}
|
|
|
|
func TestGenerateWithOpts_HealthCheckInL7Backend(t *testing.T) {
|
|
m := NewManager("")
|
|
|
|
httpRoutes := []HTTPRoute{
|
|
{Name: "api", Domain: "api.example.com", Backend: "10.0.0.5:8080", HealthPath: "/healthz"},
|
|
}
|
|
|
|
out := m.GenerateWithOpts(nil, nil, GenerateOpts{HTTPRoutes: httpRoutes})
|
|
|
|
if !strings.Contains(out, "option httpchk GET /healthz") {
|
|
t.Errorf("expected httpchk directive with health path, got:\n%s", out)
|
|
}
|
|
if !strings.Contains(out, "server s0 10.0.0.5:8080 check") {
|
|
t.Errorf("expected server line with 'check' flag for health check, got:\n%s", out)
|
|
}
|
|
}
|