cloudflare/cloudflared
Publicmirrored from https://github.com/cloudflare/cloudflaredAvailable
connection/discovery_test.go
317lines · modecode
| 1 | package connection |
| 2 | |
| 3 | import ( |
| 4 | "net" |
| 5 | "sync" |
| 6 | "testing" |
| 7 | "testing/quick" |
| 8 | "time" |
| 9 | |
| 10 | "github.com/sirupsen/logrus" |
| 11 | "github.com/stretchr/testify/assert" |
| 12 | ) |
| 13 | |
| 14 | func TestEdgeDiscovery(t *testing.T) { |
| 15 | mockAddrs := newMockAddrs(19, 2, 5) |
| 16 | netLookupSRV = mockNetLookupSRV(mockAddrs) |
| 17 | netLookupIP = mockNetLookupIP(mockAddrs) |
| 18 | |
| 19 | expectedAddrSet := map[string]bool{} |
| 20 | for _, addrs := range mockAddrs.addrMap { |
| 21 | for _, addr := range addrs { |
| 22 | expectedAddrSet[addr.String()] = true |
| 23 | } |
| 24 | } |
| 25 | |
| 26 | addrLists, err := EdgeDiscovery(logrus.New().WithFields(logrus.Fields{})) |
| 27 | assert.NoError(t, err) |
| 28 | actualAddrSet := map[string]bool{} |
| 29 | for _, addrs := range addrLists { |
| 30 | for _, addr := range addrs { |
| 31 | actualAddrSet[addr.String()] = true |
| 32 | } |
| 33 | } |
| 34 | |
| 35 | assert.Equal(t, expectedAddrSet, actualAddrSet) |
| 36 | } |
| 37 | |
| 38 | func TestAllInUse(t *testing.T) { |
| 39 | for _, testCase := range []struct { |
| 40 | regions []*region |
| 41 | expected map[string]*net.TCPAddr |
| 42 | }{ |
| 43 | { |
| 44 | regions: nil, |
| 45 | expected: map[string]*net.TCPAddr{}, |
| 46 | }, |
| 47 | { |
| 48 | regions: []*region{ |
| 49 | ®ion{inUse: map[string]*net.TCPAddr{}}, |
| 50 | ®ion{inUse: map[string]*net.TCPAddr{}}, |
| 51 | }, |
| 52 | expected: map[string]*net.TCPAddr{}, |
| 53 | }, |
| 54 | { |
| 55 | regions: []*region{ |
| 56 | ®ion{inUse: map[string]*net.TCPAddr{":1": &net.TCPAddr{Port: 1}}}, |
| 57 | ®ion{inUse: map[string]*net.TCPAddr{":4": &net.TCPAddr{Port: 4}}}, |
| 58 | }, |
| 59 | expected: map[string]*net.TCPAddr{":1": &net.TCPAddr{Port: 1}, ":4": &net.TCPAddr{Port: 4}}, |
| 60 | }, |
| 61 | } { |
| 62 | actual := allInUse(testCase.regions) |
| 63 | assert.Equal(t, testCase.expected, actual) |
| 64 | } |
| 65 | } |
| 66 | |
| 67 | func TestMakeRegions(t *testing.T) { |
| 68 | for _, testCase := range []struct { |
| 69 | addrList [][]*net.TCPAddr |
| 70 | inUse map[string]*net.TCPAddr |
| 71 | expected []*region |
| 72 | }{ |
| 73 | { |
| 74 | addrList: [][]*net.TCPAddr{}, |
| 75 | expected: nil, |
| 76 | }, |
| 77 | { |
| 78 | addrList: [][]*net.TCPAddr{ |
| 79 | []*net.TCPAddr{&net.TCPAddr{Port: 1}, &net.TCPAddr{Port: 2}}, |
| 80 | }, |
| 81 | expected: []*region{ |
| 82 | ®ion{addrs: []*net.TCPAddr{&net.TCPAddr{Port: 1}, &net.TCPAddr{Port: 2}}, inUse: map[string]*net.TCPAddr{}}, |
| 83 | }, |
| 84 | }, |
| 85 | { |
| 86 | addrList: [][]*net.TCPAddr{ |
| 87 | []*net.TCPAddr{&net.TCPAddr{Port: 1}, &net.TCPAddr{Port: 2}}, |
| 88 | []*net.TCPAddr{&net.TCPAddr{Port: 3}, &net.TCPAddr{Port: 4}}, |
| 89 | }, |
| 90 | expected: []*region{ |
| 91 | ®ion{addrs: []*net.TCPAddr{&net.TCPAddr{Port: 1}, &net.TCPAddr{Port: 2}}, inUse: map[string]*net.TCPAddr{}}, |
| 92 | ®ion{addrs: []*net.TCPAddr{&net.TCPAddr{Port: 3}, &net.TCPAddr{Port: 4}}, inUse: map[string]*net.TCPAddr{}}, |
| 93 | }, |
| 94 | }, |
| 95 | { |
| 96 | addrList: [][]*net.TCPAddr{ |
| 97 | []*net.TCPAddr{&net.TCPAddr{Port: 1}, &net.TCPAddr{Port: 2}}, |
| 98 | []*net.TCPAddr{&net.TCPAddr{Port: 3}, &net.TCPAddr{Port: 4}}, |
| 99 | }, |
| 100 | inUse: map[string]*net.TCPAddr{ |
| 101 | ":1": &net.TCPAddr{Port: 1}, |
| 102 | ":4": &net.TCPAddr{Port: 4}, |
| 103 | }, |
| 104 | expected: []*region{ |
| 105 | ®ion{addrs: []*net.TCPAddr{&net.TCPAddr{Port: 2}}, inUse: map[string]*net.TCPAddr{":1": &net.TCPAddr{Port: 1}}}, |
| 106 | ®ion{addrs: []*net.TCPAddr{&net.TCPAddr{Port: 3}}, inUse: map[string]*net.TCPAddr{":4": &net.TCPAddr{Port: 4}}}, |
| 107 | }, |
| 108 | }, |
| 109 | } { |
| 110 | actual := makeHARegions(testCase.addrList, testCase.inUse) |
| 111 | assert.Equal(t, testCase.expected, actual) |
| 112 | } |
| 113 | } |
| 114 | |
| 115 | func assertIsBalanced(t *testing.T, regions []*region) bool { |
| 116 | // Compute max(len(region.addrs) for region in regions) |
| 117 | // No region should have significantly fewer addresses than this |
| 118 | var longestAddrs int |
| 119 | { |
| 120 | longestAddrs = 0 |
| 121 | for _, region := range regions { |
| 122 | if l := len(region.addrs); l > longestAddrs { |
| 123 | longestAddrs = l |
| 124 | } |
| 125 | } |
| 126 | } |
| 127 | for _, region := range regions { |
| 128 | if len(region.addrs) == longestAddrs || len(region.addrs) == longestAddrs-1 { |
| 129 | continue |
| 130 | } |
| 131 | return assert.Fail(t, |
| 132 | "found a region with %v free addrs, while the longest addrs list is %v", |
| 133 | len(region.addrs), longestAddrs) |
| 134 | } |
| 135 | return true |
| 136 | } |
| 137 | |
| 138 | // Various end-to-end tests, run with quickcheck (i.e. the testing/quick package) |
| 139 | func TestEdgeAddrResolver(t *testing.T) { |
| 140 | concurrentReplacement := func(mockAddrs mockAddrs) bool { |
| 141 | netLookupSRV = mockNetLookupSRV(mockAddrs) |
| 142 | netLookupIP = mockNetLookupIP(mockAddrs) |
| 143 | |
| 144 | resolver, err := NewEdgeAddrResolver(logrus.New()) |
| 145 | if !assert.NoError(t, err) { |
| 146 | return false |
| 147 | } |
| 148 | assert.Equal(t, mockAddrs.numAddrs, resolver.AvailableAddrs(), |
| 149 | "every address should be initially available") |
| 150 | |
| 151 | // Create several goroutines to simulate HA connections that acquire |
| 152 | // and replace IP addresses. |
| 153 | var wg sync.WaitGroup |
| 154 | wg.Add(mockAddrs.numAddrs) |
| 155 | for i := 0; i < mockAddrs.numAddrs; i++ { |
| 156 | go func() { |
| 157 | defer wg.Done() |
| 158 | const reconnectionCount = 50 |
| 159 | for i := 0; i < reconnectionCount; i++ { |
| 160 | if resolver.AvailableAddrs() == 0 { |
| 161 | err = resolver.Refresh() |
| 162 | assert.NoError(t, err) |
| 163 | } |
| 164 | addr, err := resolver.Addr() |
| 165 | if !assert.NoError(t, err) { |
| 166 | return |
| 167 | } |
| 168 | time.Sleep(0) // allow some other goroutine to run |
| 169 | resolver.ReplaceAddr(addr) |
| 170 | time.Sleep(0) // allow some other goroutine to run |
| 171 | } |
| 172 | }() |
| 173 | } |
| 174 | wg.Wait() |
| 175 | assert.Equal(t, mockAddrs.numAddrs, resolver.AvailableAddrs(), |
| 176 | "every address should be available after replacement") |
| 177 | return !t.Failed() |
| 178 | } |
| 179 | |
| 180 | badAddrWithRefresh := func(mockAddrs mockAddrs) bool { |
| 181 | netLookupSRV = mockNetLookupSRV(mockAddrs) |
| 182 | netLookupIP = mockNetLookupIP(mockAddrs) |
| 183 | |
| 184 | resolver, err := NewEdgeAddrResolver(logrus.New()) |
| 185 | if !assert.NoError(t, err) { |
| 186 | return false |
| 187 | } |
| 188 | assert.Equal(t, mockAddrs.numAddrs, resolver.AvailableAddrs(), |
| 189 | "every address should be initially available") |
| 190 | |
| 191 | var addrs []*net.TCPAddr |
| 192 | for i := 0; i < mockAddrs.numAddrs; i++ { |
| 193 | assert.Equal(t, mockAddrs.numAddrs-i, resolver.AvailableAddrs()) |
| 194 | addr, err := resolver.Addr() |
| 195 | assert.NoError(t, err) |
| 196 | addrs = append(addrs, addr) |
| 197 | } |
| 198 | assert.Equal(t, 0, resolver.AvailableAddrs(), "all addresses should have been taken") |
| 199 | _, err = resolver.Addr() |
| 200 | assert.Error(t, err) |
| 201 | |
| 202 | anyAddr, err := resolver.AnyAddr() |
| 203 | assert.NoError(t, err, "should still be okay to call AnyAddr") |
| 204 | |
| 205 | resolver.MarkAddrBad(anyAddr) |
| 206 | |
| 207 | assert.Equal(t, 0, resolver.AvailableAddrs(), "all addresses should still be used") |
| 208 | _, err = resolver.Addr() |
| 209 | assert.Error(t, err, "all addresses should still be used") |
| 210 | |
| 211 | err = resolver.Refresh() |
| 212 | assert.NoError(t, err, "Refresh() should have worked") |
| 213 | |
| 214 | assert.Equal(t, 1, resolver.AvailableAddrs(), |
| 215 | "Refresh() should have reset the state of the 'bad' address") |
| 216 | addr, err := resolver.Addr() |
| 217 | assert.NoError(t, err) |
| 218 | assert.Equal(t, anyAddr, addr) |
| 219 | |
| 220 | _, err = resolver.Addr() |
| 221 | assert.Error(t, err, "all addresses should be used again") |
| 222 | |
| 223 | return !t.Failed() |
| 224 | } |
| 225 | |
| 226 | assert.NoError(t, quick.Check(concurrentReplacement, nil)) |
| 227 | assert.NoError(t, quick.Check(badAddrWithRefresh, nil)) |
| 228 | } |
| 229 | |
| 230 | // "White-box" test: runs Addr() and checks internal state |
| 231 | func TestEdgeAddrResolver_Addr(t *testing.T) { |
| 232 | e := &EdgeAddrResolver{regions: nil} |
| 233 | addr, err := e.Addr() |
| 234 | assert.Error(t, err) |
| 235 | |
| 236 | testRegions := func() []*region { |
| 237 | return []*region{ |
| 238 | ®ion{addrs: []*net.TCPAddr{&net.TCPAddr{Port: 1}}, inUse: map[string]*net.TCPAddr{":2": &net.TCPAddr{Port: 2}, ":3": &net.TCPAddr{Port: 3}}}, |
| 239 | ®ion{addrs: []*net.TCPAddr{&net.TCPAddr{Port: 4}, &net.TCPAddr{Port: 5}}, inUse: map[string]*net.TCPAddr{":6": &net.TCPAddr{Port: 6}}}, |
| 240 | ®ion{addrs: []*net.TCPAddr{&net.TCPAddr{Port: 7}, &net.TCPAddr{Port: 8}}, inUse: map[string]*net.TCPAddr{":9": &net.TCPAddr{Port: 9}}}, |
| 241 | } |
| 242 | } |
| 243 | e = &EdgeAddrResolver{regions: testRegions()} |
| 244 | addr, err = e.Addr() |
| 245 | assert.NoError(t, err) |
| 246 | assert.Equal(t, &net.TCPAddr{Port: 4}, addr) |
| 247 | var expected []*region |
| 248 | { |
| 249 | expected = testRegions() |
| 250 | expected[1].addrs = expected[1].addrs[1:] |
| 251 | expected[1].inUse[":4"] = &net.TCPAddr{Port: 4} |
| 252 | } |
| 253 | assert.Equal(t, expected, e.regions) |
| 254 | } |
| 255 | |
| 256 | // "White-box" test: runs AnyAddr() and checks internal state |
| 257 | func TestEdgeAddrResolver_AnyAddr(t *testing.T) { |
| 258 | e := &EdgeAddrResolver{regions: nil} |
| 259 | addr, err := e.AnyAddr() |
| 260 | assert.Error(t, err) |
| 261 | |
| 262 | e = &EdgeAddrResolver{regions: []*region{®ion{addrs: []*net.TCPAddr{&net.TCPAddr{Port: 1}}, inUse: map[string]*net.TCPAddr{":2": &net.TCPAddr{Port: 2}}}}} |
| 263 | addr, err = e.AnyAddr() |
| 264 | assert.NoError(t, err) |
| 265 | assert.Equal(t, &net.TCPAddr{Port: 1}, addr, "should have chosen the inactive address") |
| 266 | |
| 267 | e = &EdgeAddrResolver{regions: []*region{®ion{inUse: map[string]*net.TCPAddr{":1": &net.TCPAddr{Port: 1}}}}} |
| 268 | addr, err = e.AnyAddr() |
| 269 | assert.NoError(t, err) |
| 270 | assert.Equal(t, &net.TCPAddr{Port: 1}, addr, "should have chosen an active address rather than nothing") |
| 271 | } |
| 272 | |
| 273 | // "White-box" test: runs ReplaceAddr() and checks internal state |
| 274 | func TestEdgeAddrResolver_ReplaceAddr(t *testing.T) { |
| 275 | e := &EdgeAddrResolver{regions: nil} |
| 276 | e.ReplaceAddr(&net.TCPAddr{Port: 1}) // this shouldn't panic, I guess |
| 277 | |
| 278 | testRegions := func() []*region { |
| 279 | return []*region{ |
| 280 | ®ion{addrs: []*net.TCPAddr{&net.TCPAddr{Port: 1}}, inUse: map[string]*net.TCPAddr{":2": &net.TCPAddr{Port: 2}, ":3": &net.TCPAddr{Port: 3}}}, |
| 281 | ®ion{addrs: []*net.TCPAddr{&net.TCPAddr{Port: 4}, &net.TCPAddr{Port: 5}}, inUse: map[string]*net.TCPAddr{":6": &net.TCPAddr{Port: 6}}}, |
| 282 | ®ion{addrs: []*net.TCPAddr{&net.TCPAddr{Port: 7}, &net.TCPAddr{Port: 8}}, inUse: map[string]*net.TCPAddr{":9": &net.TCPAddr{Port: 9}}}, |
| 283 | } |
| 284 | } |
| 285 | e = &EdgeAddrResolver{regions: testRegions()} |
| 286 | e.ReplaceAddr(&net.TCPAddr{Port: 6}) |
| 287 | var expected []*region |
| 288 | { |
| 289 | expected = testRegions() |
| 290 | delete(expected[1].inUse, ":6") |
| 291 | expected[1].addrs = append(expected[1].addrs, &net.TCPAddr{Port: 6}) |
| 292 | } |
| 293 | assert.Equal(t, expected, e.regions) |
| 294 | } |
| 295 | |
| 296 | // "White-box" test: runs MarkAddrBad() and checks internal state |
| 297 | func TestEdgeAddrResolver_MarkAddrBad(t *testing.T) { |
| 298 | e := &EdgeAddrResolver{regions: nil} |
| 299 | e.ReplaceAddr(&net.TCPAddr{Port: 1}) // this shouldn't panic, I guess |
| 300 | |
| 301 | testRegions := func() []*region { |
| 302 | return []*region{ |
| 303 | ®ion{addrs: []*net.TCPAddr{&net.TCPAddr{Port: 1}}, inUse: map[string]*net.TCPAddr{":2": &net.TCPAddr{Port: 2}, ":3": &net.TCPAddr{Port: 3}}}, |
| 304 | ®ion{addrs: []*net.TCPAddr{&net.TCPAddr{Port: 4}, &net.TCPAddr{Port: 5}}, inUse: map[string]*net.TCPAddr{":6": &net.TCPAddr{Port: 6}}}, |
| 305 | ®ion{addrs: []*net.TCPAddr{&net.TCPAddr{Port: 7}, &net.TCPAddr{Port: 8}}, inUse: map[string]*net.TCPAddr{":9": &net.TCPAddr{Port: 9}}}, |
| 306 | } |
| 307 | } |
| 308 | e = &EdgeAddrResolver{regions: testRegions()} |
| 309 | e.MarkAddrBad(&net.TCPAddr{Port: 6}) |
| 310 | var expected []*region |
| 311 | { |
| 312 | expected = testRegions() |
| 313 | delete(expected[1].inUse, ":6") |
| 314 | expected[1].bad = append(expected[1].bad, &net.TCPAddr{Port: 6}) |
| 315 | } |
| 316 | assert.Equal(t, expected, e.regions) |
| 317 | } |
| 318 | |