github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/core/network/hostport_test.go (about) 1 // Copyright 2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package network_test 5 6 import ( 7 "fmt" 8 "sort" 9 10 "github.com/juju/errors" 11 jc "github.com/juju/testing/checkers" 12 gc "gopkg.in/check.v1" 13 14 "github.com/juju/juju/core/network" 15 coretesting "github.com/juju/juju/testing" 16 ) 17 18 type HostPortSuite struct { 19 coretesting.BaseSuite 20 } 21 22 var _ = gc.Suite(&HostPortSuite{}) 23 24 func (s *HostPortSuite) TestFilterUnusableHostPorts(c *gc.C) { 25 // The order is preserved, but machine- and link-local addresses 26 // are dropped. 27 expected := append( 28 network.NewSpaceHostPorts(1234, 29 "localhost", 30 "example.com", 31 "example.org", 32 "2001:db8::2", 33 "example.net", 34 "invalid host", 35 "fd00::22", 36 "2001:db8::1", 37 "0.1.2.0", 38 "2001:db8::1", 39 "localhost", 40 "10.0.0.1", 41 "fc00::1", 42 "172.16.0.1", 43 "8.8.8.8", 44 "7.8.8.8", 45 ), 46 network.NewSpaceHostPorts(9999, 47 "10.0.0.1", 48 "2001:db8::1", // public 49 )..., 50 ).HostPorts() 51 52 result := s.makeHostPorts().HostPorts().FilterUnusable() 53 c.Assert(result, gc.HasLen, len(expected)) 54 c.Assert(result, jc.DeepEquals, expected) 55 } 56 57 func (*HostPortSuite) TestCollapseToHostPorts(c *gc.C) { 58 servers := []network.MachineHostPorts{ 59 network.NewMachineHostPorts(1234, 60 "0.1.2.3", "10.0.1.2", "fc00::1", "2001:db8::1", "::1", 61 "127.0.0.1", "localhost", "fe80::123", "example.com", 62 ), 63 network.NewMachineHostPorts(4321, 64 "8.8.8.8", "1.2.3.4", "fc00::2", "127.0.0.1", "foo", 65 ), 66 network.NewMachineHostPorts(9999, 67 "localhost", "127.0.0.1", 68 ), 69 } 70 expected := append(servers[0], append(servers[1], servers[2]...)...).HostPorts() 71 result := network.CollapseToHostPorts(servers) 72 c.Assert(result, gc.HasLen, len(servers[0])+len(servers[1])+len(servers[2])) 73 c.Assert(result, jc.DeepEquals, expected) 74 } 75 76 func (s *HostPortSuite) TestEnsureFirstHostPort(c *gc.C) { 77 first := network.NewSpaceHostPorts(1234, "1.2.3.4")[0] 78 79 // Without any HostPorts, it still works. 80 hps := network.EnsureFirstHostPort(first, []network.SpaceHostPort{}) 81 c.Assert(hps, jc.DeepEquals, network.SpaceHostPorts{first}) 82 83 // If already there, no changes happen. 84 hps = s.makeHostPorts() 85 result := network.EnsureFirstHostPort(hps[0], hps) 86 c.Assert(result, jc.DeepEquals, hps) 87 88 // If not at the top, pop it up and put it on top. 89 firstLast := append(hps, first) 90 result = network.EnsureFirstHostPort(first, firstLast) 91 c.Assert(result, jc.DeepEquals, append(network.SpaceHostPorts{first}, hps...)) 92 } 93 94 func (*HostPortSuite) TestNewHostPorts(c *gc.C) { 95 addrs := []string{"0.1.2.3", "fc00::1", "::1", "example.com"} 96 expected := network.SpaceAddressesWithPort( 97 network.NewSpaceAddresses(addrs...), 42, 98 ) 99 result := network.NewSpaceHostPorts(42, addrs...) 100 c.Assert(result, gc.HasLen, len(addrs)) 101 c.Assert(result, jc.DeepEquals, expected) 102 } 103 104 func (*HostPortSuite) TestParseHostPortsErrors(c *gc.C) { 105 for i, test := range []struct { 106 input string 107 err string 108 }{{ 109 input: "", 110 err: `cannot parse "" as address:port: .*missing port in address.*`, 111 }, { 112 input: " ", 113 err: `cannot parse " " as address:port: .*missing port in address.*`, 114 }, { 115 input: ":", 116 err: `cannot parse ":" port: strconv.(ParseInt|Atoi): parsing "": invalid syntax`, 117 }, { 118 input: "host", 119 err: `cannot parse "host" as address:port: .*missing port in address.*`, 120 }, { 121 input: "host:port", 122 err: `cannot parse "host:port" port: strconv.(ParseInt|Atoi): parsing "port": invalid syntax`, 123 }, { 124 input: "::1", 125 err: `cannot parse "::1" as address:port: .*too many colons in address.*`, 126 }, { 127 input: "1.2.3.4", 128 err: `cannot parse "1.2.3.4" as address:port: .*missing port in address.*`, 129 }, { 130 input: "1.2.3.4:foo", 131 err: `cannot parse "1.2.3.4:foo" port: strconv.(ParseInt|Atoi): parsing "foo": invalid syntax`, 132 }} { 133 c.Logf("test %d: input %q", i, test.input) 134 // First test all error cases with a single argument. 135 hps, err := network.ParseMachineHostPort(test.input) 136 c.Check(err, gc.ErrorMatches, test.err) 137 c.Check(hps, gc.IsNil) 138 } 139 // Finally, test with mixed valid and invalid args. 140 hps, err := network.ParseProviderHostPorts("1.2.3.4:42", "[fc00::1]:12", "foo") 141 c.Assert(err, gc.ErrorMatches, `cannot parse "foo" as address:port: .*missing port in address.*`) 142 c.Assert(hps, gc.IsNil) 143 } 144 145 func (*HostPortSuite) TestParseProviderHostPortsSuccess(c *gc.C) { 146 for i, test := range []struct { 147 args []string 148 expect network.ProviderHostPorts 149 }{{ 150 args: nil, 151 expect: []network.ProviderHostPort{}, 152 }, { 153 args: []string{"1.2.3.4:42"}, 154 expect: []network.ProviderHostPort{{network.NewMachineAddress("1.2.3.4").AsProviderAddress(), 42}}, 155 }, { 156 args: []string{"[fc00::1]:1234"}, 157 expect: []network.ProviderHostPort{{network.NewMachineAddress("fc00::1").AsProviderAddress(), 1234}}, 158 }, { 159 args: []string{"[fc00::1]:1234", "127.0.0.1:4321", "example.com:42"}, 160 expect: []network.ProviderHostPort{ 161 {network.NewMachineAddress("fc00::1").AsProviderAddress(), 1234}, 162 {network.NewMachineAddress("127.0.0.1").AsProviderAddress(), 4321}, 163 {network.NewMachineAddress("example.com").AsProviderAddress(), 42}, 164 }, 165 }} { 166 c.Logf("test %d: args %v", i, test.args) 167 hps, err := network.ParseProviderHostPorts(test.args...) 168 c.Check(err, jc.ErrorIsNil) 169 c.Check(hps, jc.DeepEquals, test.expect) 170 } 171 } 172 173 func (*HostPortSuite) TestAddressesWithPort(c *gc.C) { 174 addrs := network.NewSpaceAddresses("0.1.2.3", "0.2.4.6") 175 hps := network.SpaceAddressesWithPort(addrs, 999) 176 c.Assert(hps, jc.DeepEquals, network.SpaceHostPorts{{ 177 SpaceAddress: network.NewSpaceAddress("0.1.2.3"), 178 NetPort: 999, 179 }, { 180 SpaceAddress: network.NewSpaceAddress("0.2.4.6"), 181 NetPort: 999, 182 }}) 183 } 184 185 func (s *HostPortSuite) assertHostPorts(c *gc.C, actual network.HostPorts, expected ...string) { 186 c.Assert(actual.Strings(), jc.DeepEquals, expected) 187 } 188 189 func (s *HostPortSuite) TestSortHostPorts(c *gc.C) { 190 hps := s.makeHostPorts() 191 sort.Sort(hps) 192 s.assertHostPorts(c, hps.HostPorts(), 193 // Public IPv4 addresses on top. 194 "0.1.2.0:1234", 195 "7.8.8.8:1234", 196 "8.8.8.8:1234", 197 // After that public IPv6 addresses. 198 "[2001:db8::1]:1234", 199 "[2001:db8::1]:1234", 200 "[2001:db8::1]:9999", 201 "[2001:db8::2]:1234", 202 // Then hostnames. 203 "example.com:1234", 204 "example.net:1234", 205 "example.org:1234", 206 "invalid host:1234", 207 "localhost:1234", 208 "localhost:1234", 209 // Then IPv4 cloud-local addresses. 210 "10.0.0.1:1234", 211 "10.0.0.1:9999", 212 "172.16.0.1:1234", 213 // Then IPv6 cloud-local addresses. 214 "[fc00::1]:1234", 215 "[fd00::22]:1234", 216 // Then machine-local IPv4 addresses. 217 "127.0.0.1:1234", 218 "127.0.0.1:1234", 219 "127.0.0.1:9999", 220 "127.0.1.1:1234", 221 // Then machine-local IPv6 addresses. 222 "[::1]:1234", 223 "[::1]:1234", 224 // Then link-local IPv4 addresses. 225 "169.254.1.1:1234", 226 "169.254.1.2:1234", 227 // Finally, link-local IPv6 addresses. 228 "[fe80::2]:1234", 229 "[fe80::2]:9999", 230 "[ff01::22]:1234", 231 ) 232 } 233 234 var netAddrTests = []struct { 235 addr network.SpaceAddress 236 port int 237 expect string 238 }{{ 239 addr: network.NewSpaceAddress("0.1.2.3"), 240 port: 99, 241 expect: "0.1.2.3:99", 242 }, { 243 addr: network.NewSpaceAddress("2001:DB8::1"), 244 port: 100, 245 expect: "[2001:DB8::1]:100", 246 }, { 247 addr: network.NewSpaceAddress("172.16.0.1"), 248 port: 52, 249 expect: "172.16.0.1:52", 250 }, { 251 addr: network.NewSpaceAddress("fc00::2"), 252 port: 1111, 253 expect: "[fc00::2]:1111", 254 }, { 255 addr: network.NewSpaceAddress("example.com"), 256 port: 9999, 257 expect: "example.com:9999", 258 }, { 259 addr: network.NewSpaceAddress("example.com", network.WithScope(network.ScopePublic)), 260 port: 1234, 261 expect: "example.com:1234", 262 }, { 263 addr: network.NewSpaceAddress("169.254.1.2"), 264 port: 123, 265 expect: "169.254.1.2:123", 266 }, { 267 addr: network.NewSpaceAddress("fe80::222"), 268 port: 321, 269 expect: "[fe80::222]:321", 270 }, { 271 addr: network.NewSpaceAddress("127.0.0.2"), 272 port: 121, 273 expect: "127.0.0.2:121", 274 }, { 275 addr: network.NewSpaceAddress("::1"), 276 port: 111, 277 expect: "[::1]:111", 278 }} 279 280 func (*HostPortSuite) TestDialAddressAndString(c *gc.C) { 281 for i, test := range netAddrTests { 282 c.Logf("test %d: %q", i, test.addr) 283 hp := network.SpaceHostPort{ 284 SpaceAddress: test.addr, 285 NetPort: network.NetPort(test.port), 286 } 287 c.Check(network.DialAddress(hp), gc.Equals, test.expect) 288 c.Check(hp.String(), gc.Equals, test.expect) 289 c.Check(hp.GoString(), gc.Equals, test.expect) 290 } 291 } 292 293 func (s *HostPortSuite) TestHostPortsToStrings(c *gc.C) { 294 hps := s.makeHostPorts() 295 strHPs := hps.HostPorts().Strings() 296 c.Assert(strHPs, gc.HasLen, len(hps)) 297 c.Assert(strHPs, jc.DeepEquals, []string{ 298 "127.0.0.1:1234", 299 "localhost:1234", 300 "example.com:1234", 301 "127.0.1.1:1234", 302 "example.org:1234", 303 "[2001:db8::2]:1234", 304 "169.254.1.1:1234", 305 "example.net:1234", 306 "invalid host:1234", 307 "[fd00::22]:1234", 308 "127.0.0.1:1234", 309 "[2001:db8::1]:1234", 310 "169.254.1.2:1234", 311 "[ff01::22]:1234", 312 "0.1.2.0:1234", 313 "[2001:db8::1]:1234", 314 "localhost:1234", 315 "10.0.0.1:1234", 316 "[::1]:1234", 317 "[fc00::1]:1234", 318 "[fe80::2]:1234", 319 "172.16.0.1:1234", 320 "[::1]:1234", 321 "8.8.8.8:1234", 322 "7.8.8.8:1234", 323 "127.0.0.1:9999", 324 "10.0.0.1:9999", 325 "[2001:db8::1]:9999", 326 "[fe80::2]:9999", 327 }) 328 } 329 330 func (*HostPortSuite) makeHostPorts() network.SpaceHostPorts { 331 return append( 332 network.NewSpaceHostPorts(1234, 333 "127.0.0.1", // machine-local 334 "localhost", // hostname 335 "example.com", // hostname 336 "127.0.1.1", // machine-local 337 "example.org", // hostname 338 "2001:db8::2", // public 339 "169.254.1.1", // link-local 340 "example.net", // hostname 341 "invalid host", // hostname 342 "fd00::22", // cloud-local 343 "127.0.0.1", // machine-local 344 "2001:db8::1", // public 345 "169.254.1.2", // link-local 346 "ff01::22", // link-local 347 "0.1.2.0", // public 348 "2001:db8::1", // public 349 "localhost", // hostname 350 "10.0.0.1", // cloud-local 351 "::1", // machine-local 352 "fc00::1", // cloud-local 353 "fe80::2", // link-local 354 "172.16.0.1", // cloud-local 355 "::1", // machine-local 356 "8.8.8.8", // public 357 "7.8.8.8", // public 358 ), 359 network.NewSpaceHostPorts(9999, 360 "127.0.0.1", // machine-local 361 "10.0.0.1", // cloud-local 362 "2001:db8::1", // public 363 "fe80::2", // link-local 364 )..., 365 ) 366 } 367 368 func (s *HostPortSuite) TestUniqueHostPortsSimpleInput(c *gc.C) { 369 input := network.NewSpaceHostPorts(1234, "127.0.0.1", "::1") 370 expected := input.HostPorts() 371 c.Assert(input.HostPorts().Unique(), jc.DeepEquals, expected) 372 } 373 374 func (s *HostPortSuite) TestUniqueHostPortsOnlyDuplicates(c *gc.C) { 375 input := s.manyMachineHostPorts(c, 10000, nil) // use IANA reserved port 376 expected := input[0:1].HostPorts() 377 c.Assert(input.HostPorts().Unique(), jc.DeepEquals, expected) 378 } 379 380 func (s *HostPortSuite) TestUniqueHostPortsHugeUniqueInput(c *gc.C) { 381 input := s.manyMachineHostPorts(c, maxTCPPort, func(port int) string { 382 return fmt.Sprintf("127.1.0.1:%d", port) 383 }) 384 expected := input.HostPorts() 385 c.Assert(input.HostPorts().Unique(), jc.DeepEquals, expected) 386 } 387 388 const maxTCPPort = 65535 389 390 func (s *HostPortSuite) manyMachineHostPorts( 391 c *gc.C, count int, addressFunc func(index int) string) network.MachineHostPorts { 392 if addressFunc == nil { 393 addressFunc = func(_ int) string { 394 return "127.0.0.1:49151" // all use the same IANA reserved port. 395 } 396 } 397 398 results := make([]network.MachineHostPort, count) 399 for i := range results { 400 hostPort, err := network.ParseMachineHostPort(addressFunc(i)) 401 c.Assert(err, jc.ErrorIsNil) 402 results[i] = *hostPort 403 } 404 return results 405 } 406 407 type selectInternalHostPortsTest struct { 408 about string 409 addresses network.SpaceHostPorts 410 expected []string 411 } 412 413 var prioritizeInternalHostPortsTests = []selectInternalHostPortsTest{{ 414 "no addresses gives empty string result", 415 []network.SpaceHostPort{}, 416 []string{}, 417 }, { 418 "a public IPv4 address is selected", 419 []network.SpaceHostPort{ 420 {network.NewSpaceAddress("8.8.8.8", network.WithScope(network.ScopePublic)), 9999}, 421 }, 422 []string{"8.8.8.8:9999"}, 423 }, { 424 "cloud local IPv4 addresses are selected", 425 []network.SpaceHostPort{ 426 {network.NewSpaceAddress("10.1.0.1", network.WithScope(network.ScopeCloudLocal)), 8888}, 427 {network.NewSpaceAddress("8.8.8.8", network.WithScope(network.ScopePublic)), 123}, 428 {network.NewSpaceAddress("10.0.0.1", network.WithScope(network.ScopeCloudLocal)), 1234}, 429 }, 430 []string{"10.1.0.1:8888", "10.0.0.1:1234", "8.8.8.8:123"}, 431 }, { 432 "a machine local or link-local address is not selected", 433 []network.SpaceHostPort{ 434 {network.NewSpaceAddress("127.0.0.1", network.WithScope(network.ScopeMachineLocal)), 111}, 435 {network.NewSpaceAddress("::1", network.WithScope(network.ScopeMachineLocal)), 222}, 436 {network.NewSpaceAddress("fe80::1", network.WithScope(network.ScopeLinkLocal)), 333}, 437 }, 438 []string{}, 439 }, { 440 "cloud local addresses are preferred to a public addresses", 441 []network.SpaceHostPort{ 442 {network.NewSpaceAddress("2001:db8::1", network.WithScope(network.ScopePublic)), 123}, 443 {network.NewSpaceAddress("fc00::1", network.WithScope(network.ScopeCloudLocal)), 123}, 444 {network.NewSpaceAddress("8.8.8.8", network.WithScope(network.ScopePublic)), 123}, 445 {network.NewSpaceAddress("10.0.0.1", network.WithScope(network.ScopeCloudLocal)), 4444}, 446 }, 447 []string{"10.0.0.1:4444", "[fc00::1]:123", "8.8.8.8:123", "[2001:db8::1]:123"}, 448 }} 449 450 func (s *HostPortSuite) TestPrioritizeInternalHostPorts(c *gc.C) { 451 for i, t := range prioritizeInternalHostPortsTests { 452 c.Logf("test %d: %s", i, t.about) 453 prioritized := t.addresses.HostPorts().PrioritizedForScope(network.ScopeMatchCloudLocal) 454 c.Check(prioritized, gc.DeepEquals, t.expected) 455 } 456 } 457 458 var selectInternalHostPortsTests = []selectInternalHostPortsTest{{ 459 "no addresses gives empty string result", 460 []network.SpaceHostPort{}, 461 []string{}, 462 }, { 463 "a public IPv4 address is selected", 464 []network.SpaceHostPort{ 465 {network.NewSpaceAddress("8.8.8.8", network.WithScope(network.ScopePublic)), 9999}, 466 }, 467 []string{"8.8.8.8:9999"}, 468 }, { 469 "cloud local IPv4 addresses are selected", 470 []network.SpaceHostPort{ 471 {network.NewSpaceAddress("10.1.0.1", network.WithScope(network.ScopeCloudLocal)), 8888}, 472 {network.NewSpaceAddress("8.8.8.8", network.WithScope(network.ScopePublic)), 123}, 473 {network.NewSpaceAddress("10.0.0.1", network.WithScope(network.ScopeCloudLocal)), 1234}, 474 }, 475 []string{"10.1.0.1:8888", "10.0.0.1:1234"}, 476 }, { 477 "a machine local or link-local address is not selected", 478 []network.SpaceHostPort{ 479 {network.NewSpaceAddress("127.0.0.1", network.WithScope(network.ScopeMachineLocal)), 111}, 480 {network.NewSpaceAddress("::1", network.WithScope(network.ScopeMachineLocal)), 222}, 481 {network.NewSpaceAddress("fe80::1", network.WithScope(network.ScopeLinkLocal)), 333}, 482 }, 483 []string{}, 484 }, { 485 "cloud local IPv4 addresses are preferred to a public addresses", 486 []network.SpaceHostPort{ 487 {network.NewSpaceAddress("2001:db8::1", network.WithScope(network.ScopePublic)), 123}, 488 {network.NewSpaceAddress("fc00::1", network.WithScope(network.ScopeCloudLocal)), 123}, 489 {network.NewSpaceAddress("8.8.8.8", network.WithScope(network.ScopePublic)), 123}, 490 {network.NewSpaceAddress("10.0.0.1", network.WithScope(network.ScopeCloudLocal)), 4444}, 491 }, 492 []string{"10.0.0.1:4444"}, 493 }, { 494 "cloud local IPv6 addresses are preferred to a public addresses", 495 []network.SpaceHostPort{ 496 {network.NewSpaceAddress("2001:db8::1", network.WithScope(network.ScopePublic)), 123}, 497 {network.NewSpaceAddress("fc00::1", network.WithScope(network.ScopeCloudLocal)), 123}, 498 {network.NewSpaceAddress("8.8.8.8", network.WithScope(network.ScopePublic)), 123}, 499 }, 500 []string{"[fc00::1]:123"}, 501 }} 502 503 func (s *HostPortSuite) TestSelectInternalHostPorts(c *gc.C) { 504 for i, t := range selectInternalHostPortsTests { 505 c.Logf("test %d: %s", i, t.about) 506 c.Check(t.addresses.AllMatchingScope(network.ScopeMatchCloudLocal), gc.DeepEquals, t.expected) 507 } 508 } 509 510 func (s *HostPortSuite) TestSpaceHostPortsToProviderHostPorts(c *gc.C) { 511 // Check success. 512 hps := network.NewSpaceHostPorts(1234, "1.2.3.4", "2.3.4.5", "3.4.5.6") 513 hps[0].SpaceID = "1" 514 hps[1].SpaceID = "2" 515 516 exp := network.ProviderHostPorts{ 517 { 518 ProviderAddress: network.NewMachineAddress("1.2.3.4").AsProviderAddress(network.WithSpaceName("space-one")), 519 NetPort: 1234, 520 }, 521 { 522 ProviderAddress: network.NewMachineAddress("2.3.4.5").AsProviderAddress(network.WithSpaceName("space-two")), 523 NetPort: 1234, 524 }, 525 { 526 ProviderAddress: network.NewMachineAddress("3.4.5.6").AsProviderAddress(), 527 NetPort: 1234, 528 }, 529 } 530 // Only the first address in the lookup has a provider ID. 531 exp[0].ProviderSpaceID = "p1" 532 533 res, err := hps.ToProviderHostPorts(stubLookup{}) 534 c.Assert(err, jc.ErrorIsNil) 535 c.Check(res, jc.SameContents, exp) 536 537 // Add a host/port in a space that the lookup will not resolve. 538 hps = append(hps, network.NewSpaceHostPorts(3456, "4.5.6.7")...) 539 hps[3].SpaceID = "3" 540 _, err = hps.ToProviderHostPorts(stubLookup{}) 541 c.Assert(err, jc.Satisfies, errors.IsNotFound) 542 }