package allregions import ( "net" "testing" "time" "github.com/stretchr/testify/assert" ) func makeAddrSet(addrs []*EdgeAddr) AddrSet { addrSet := make(AddrSet, len(addrs)) for _, addr := range addrs { addrSet[addr] = Unused() } return addrSet } func TestRegion_New(t *testing.T) { tests := []struct { name string addrs []*EdgeAddr mode ConfigIPVersion expectedAddrs int primary AddrSet secondary AddrSet }{ { name: "IPv4 addresses with IPv4Only", addrs: v4Addrs, mode: IPv4Only, expectedAddrs: len(v4Addrs), primary: makeAddrSet(v4Addrs), secondary: AddrSet{}, }, { name: "IPv6 addresses with IPv4Only", addrs: v6Addrs, mode: IPv4Only, expectedAddrs: 0, primary: AddrSet{}, secondary: AddrSet{}, }, { name: "IPv6 addresses with IPv6Only", addrs: v6Addrs, mode: IPv6Only, expectedAddrs: len(v6Addrs), primary: makeAddrSet(v6Addrs), secondary: AddrSet{}, }, { name: "IPv6 addresses with IPv4Only", addrs: v6Addrs, mode: IPv4Only, expectedAddrs: 0, primary: AddrSet{}, secondary: AddrSet{}, }, { name: "IPv4 (first) and IPv6 addresses with Auto", addrs: append(v4Addrs, v6Addrs...), mode: Auto, expectedAddrs: len(v4Addrs), primary: makeAddrSet(v4Addrs), secondary: makeAddrSet(v6Addrs), }, { name: "IPv6 (first) and IPv4 addresses with Auto", addrs: append(v6Addrs, v4Addrs...), mode: Auto, expectedAddrs: len(v6Addrs), primary: makeAddrSet(v6Addrs), secondary: makeAddrSet(v4Addrs), }, { name: "IPv4 addresses with Auto", addrs: v4Addrs, mode: Auto, expectedAddrs: len(v4Addrs), primary: makeAddrSet(v4Addrs), secondary: AddrSet{}, }, { name: "IPv6 addresses with Auto", addrs: v6Addrs, mode: Auto, expectedAddrs: len(v6Addrs), primary: makeAddrSet(v6Addrs), secondary: AddrSet{}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { r := NewRegion(tt.addrs, tt.mode) assert.Equal(t, tt.expectedAddrs, r.AvailableAddrs()) assert.Equal(t, tt.primary, r.primary) assert.Equal(t, tt.secondary, r.secondary) }) } } func TestRegion_AnyAddress_EmptyActiveSet(t *testing.T) { tests := []struct { name string addrs []*EdgeAddr mode ConfigIPVersion }{ { name: "IPv6 addresses with IPv4Only", addrs: v6Addrs, mode: IPv4Only, }, { name: "IPv4 addresses with IPv6Only", addrs: v4Addrs, mode: IPv6Only, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { r := NewRegion(tt.addrs, tt.mode) addr := r.GetAnyAddress() assert.Nil(t, addr) addr = r.AssignAnyAddress(0, nil) assert.Nil(t, addr) }) } } func TestRegion_AssignAnyAddress_FullyUsedActiveSet(t *testing.T) { tests := []struct { name string addrs []*EdgeAddr mode ConfigIPVersion }{ { name: "IPv6 addresses with IPv6Only", addrs: v6Addrs, mode: IPv6Only, }, { name: "IPv4 addresses with IPv4Only", addrs: v4Addrs, mode: IPv4Only, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { r := NewRegion(tt.addrs, tt.mode) total := r.active.AvailableAddrs() for i := 0; i < total; i++ { addr := r.AssignAnyAddress(i, nil) assert.NotNil(t, addr) } addr := r.AssignAnyAddress(9, nil) assert.Nil(t, addr) }) } } var giveBackTests = []struct { name string addrs []*EdgeAddr mode ConfigIPVersion expectedAddrs int primary AddrSet secondary AddrSet primarySwap bool }{ { name: "IPv4 addresses with IPv4Only", addrs: v4Addrs, mode: IPv4Only, expectedAddrs: len(v4Addrs), primary: makeAddrSet(v4Addrs), secondary: AddrSet{}, primarySwap: false, }, { name: "IPv6 addresses with IPv6Only", addrs: v6Addrs, mode: IPv6Only, expectedAddrs: len(v6Addrs), primary: makeAddrSet(v6Addrs), secondary: AddrSet{}, primarySwap: false, }, { name: "IPv4 (first) and IPv6 addresses with Auto", addrs: append(v4Addrs, v6Addrs...), mode: Auto, expectedAddrs: len(v4Addrs), primary: makeAddrSet(v4Addrs), secondary: makeAddrSet(v6Addrs), primarySwap: false, }, { name: "IPv6 (first) and IPv4 addresses with Auto", addrs: append(v6Addrs, v4Addrs...), mode: Auto, expectedAddrs: len(v6Addrs), primary: makeAddrSet(v6Addrs), secondary: makeAddrSet(v4Addrs), primarySwap: true, }, { name: "IPv4 addresses with Auto", addrs: v4Addrs, mode: Auto, expectedAddrs: len(v4Addrs), primary: makeAddrSet(v4Addrs), secondary: AddrSet{}, primarySwap: false, }, { name: "IPv6 addresses with Auto", addrs: v6Addrs, mode: Auto, expectedAddrs: len(v6Addrs), primary: makeAddrSet(v6Addrs), secondary: AddrSet{}, primarySwap: false, }, } func TestRegion_GiveBack_NoConnectivityError(t *testing.T) { for _, tt := range giveBackTests { t.Run(tt.name, func(t *testing.T) { r := NewRegion(tt.addrs, tt.mode) addr := r.AssignAnyAddress(0, nil) assert.NotNil(t, addr) assert.True(t, r.GiveBack(addr, false)) }) } } func TestRegion_GiveBack_ForeignAddr(t *testing.T) { invalid := EdgeAddr{ TCP: &net.TCPAddr{ IP: net.ParseIP("123.4.5.0"), Port: 8000, Zone: "", }, UDP: &net.UDPAddr{ IP: net.ParseIP("123.4.5.0"), Port: 8000, Zone: "", }, IPVersion: V4, } for _, tt := range giveBackTests { t.Run(tt.name, func(t *testing.T) { r := NewRegion(tt.addrs, tt.mode) assert.False(t, r.GiveBack(&invalid, false)) assert.False(t, r.GiveBack(&invalid, true)) }) } } func TestRegion_GiveBack_SwapPrimary(t *testing.T) { for _, tt := range giveBackTests { t.Run(tt.name, func(t *testing.T) { r := NewRegion(tt.addrs, tt.mode) addr := r.AssignAnyAddress(0, nil) assert.NotNil(t, addr) assert.True(t, r.GiveBack(addr, true)) assert.Equal(t, tt.primarySwap, !r.primaryIsActive) if tt.primarySwap { assert.Equal(t, r.secondary, r.active) assert.False(t, r.primaryTimeout.IsZero()) } else { assert.Equal(t, r.primary, r.active) assert.True(t, r.primaryTimeout.IsZero()) } }) } } func TestRegion_GiveBack_IPv4_ResetPrimary(t *testing.T) { r := NewRegion(append(v6Addrs, v4Addrs...), Auto) // Exhaust all IPv6 addresses a0 := r.AssignAnyAddress(0, nil) a1 := r.AssignAnyAddress(1, nil) a2 := r.AssignAnyAddress(2, nil) a3 := r.AssignAnyAddress(3, nil) assert.NotNil(t, a0) assert.NotNil(t, a1) assert.NotNil(t, a2) assert.NotNil(t, a3) // Give back the first IPv6 address to fallback to secondary IPv4 address set assert.True(t, r.GiveBack(a0, true)) assert.False(t, r.primaryIsActive) // Give back another IPv6 address assert.True(t, r.GiveBack(a1, true)) // Primary shouldn't change assert.False(t, r.primaryIsActive) // Request an address (should be IPv4 from secondary) a4_v4 := r.AssignAnyAddress(4, nil) assert.NotNil(t, a4_v4) assert.Equal(t, V4, a4_v4.IPVersion) a5_v4 := r.AssignAnyAddress(5, nil) assert.NotNil(t, a5_v4) assert.Equal(t, V4, a5_v4.IPVersion) a6_v4 := r.AssignAnyAddress(6, nil) assert.NotNil(t, a6_v4) assert.Equal(t, V4, a6_v4.IPVersion) // Return IPv4 address (without failure) // Primary shouldn't change because it is not a connectivity failure assert.True(t, r.GiveBack(a4_v4, false)) assert.False(t, r.primaryIsActive) // Return IPv4 address (with failure) // Primary should change because it is a connectivity failure assert.True(t, r.GiveBack(a5_v4, true)) assert.True(t, r.primaryIsActive) // Return IPv4 address (with failure) // Primary shouldn't change because the address is returned to the inactive // secondary address set assert.True(t, r.GiveBack(a6_v4, true)) assert.True(t, r.primaryIsActive) // Return IPv6 address (without failure) // Primary shoudn't change because it is not a connectivity failure assert.True(t, r.GiveBack(a2, false)) assert.True(t, r.primaryIsActive) } func TestRegion_GiveBack_Timeout(t *testing.T) { r := NewRegion(append(v6Addrs, v4Addrs...), Auto) a0 := r.AssignAnyAddress(0, nil) a1 := r.AssignAnyAddress(1, nil) a2 := r.AssignAnyAddress(2, nil) assert.NotNil(t, a0) assert.NotNil(t, a1) assert.NotNil(t, a2) // Give back IPv6 address to set timeout assert.True(t, r.GiveBack(a0, true)) assert.False(t, r.primaryIsActive) assert.False(t, r.primaryTimeout.IsZero()) // Request an address (should be IPv4 from secondary) a3_v4 := r.AssignAnyAddress(3, nil) assert.NotNil(t, a3_v4) assert.Equal(t, V4, a3_v4.IPVersion) assert.False(t, r.primaryIsActive) // Give back IPv6 address inside timeout (no change) assert.True(t, r.GiveBack(a2, true)) assert.False(t, r.primaryIsActive) assert.False(t, r.primaryTimeout.IsZero()) // Accelerate timeout r.primaryTimeout = time.Now().Add(-time.Minute) // Return IPv6 address assert.True(t, r.GiveBack(a1, true)) assert.True(t, r.primaryIsActive) // Returning an IPv4 address after primary is active shouldn't change primary // even with a connectivity error assert.True(t, r.GiveBack(a3_v4, true)) assert.True(t, r.primaryIsActive) }