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  }