github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/cmd/juju/commands/endpoint_test.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package commands 5 6 import ( 7 jc "github.com/juju/testing/checkers" 8 gc "gopkg.in/check.v1" 9 10 "github.com/juju/juju/cmd/envcmd" 11 "github.com/juju/juju/environs/config" 12 "github.com/juju/juju/environs/configstore" 13 envtesting "github.com/juju/juju/environs/testing" 14 "github.com/juju/juju/instance" 15 "github.com/juju/juju/juju/testing" 16 "github.com/juju/juju/network" 17 "github.com/juju/juju/provider/dummy" 18 coretesting "github.com/juju/juju/testing" 19 ) 20 21 type EndpointSuite struct { 22 testing.JujuConnSuite 23 24 restoreTimeouts func() 25 } 26 27 var _ = gc.Suite(&EndpointSuite{}) 28 29 func (s *EndpointSuite) SetUpSuite(c *gc.C) { 30 // Use very short attempt strategies when getting instance addresses. 31 s.restoreTimeouts = envtesting.PatchAttemptStrategies() 32 s.JujuConnSuite.SetUpSuite(c) 33 } 34 35 func (s *EndpointSuite) TearDownSuite(c *gc.C) { 36 s.JujuConnSuite.TearDownSuite(c) 37 s.restoreTimeouts() 38 } 39 40 func (s *EndpointSuite) TestNoEndpoints(c *gc.C) { 41 // Reset all addresses. 42 s.setCachedAPIAddresses(c) 43 s.setServerAPIAddresses(c) 44 s.assertCachedAddresses(c) 45 46 stdout, stderr, err := s.runCommand(c) 47 c.Assert(err, gc.ErrorMatches, "no API endpoints available") 48 c.Assert(stdout, gc.Equals, "") 49 c.Assert(stderr, gc.Equals, "") 50 51 s.assertCachedAddresses(c) 52 } 53 54 func (s *EndpointSuite) TestCachedAddressesUsedIfAvailable(c *gc.C) { 55 addresses := network.NewHostPorts(1234, 56 "10.0.0.1:1234", 57 "[2001:db8::1]:1234", 58 "0.1.2.3:1234", 59 "[fc00::1]:1234", 60 ) 61 // Set the cached addresses. 62 s.setCachedAPIAddresses(c, addresses...) 63 // Clear instance/state addresses to ensure we can't connect to 64 // the API server. 65 s.setServerAPIAddresses(c) 66 67 testRun := func(i int, envPreferIPv6, bootPreferIPv6 bool) { 68 c.Logf( 69 "\ntest %d: prefer-ipv6 environ=%v, bootstrap=%v", 70 i, envPreferIPv6, bootPreferIPv6, 71 ) 72 s.setPreferIPv6EnvironConfig(c, envPreferIPv6) 73 s.setPreferIPv6BootstrapConfig(c, bootPreferIPv6) 74 75 // Without arguments, verify the first cached address is returned. 76 s.runAndCheckOutput(c, "smart", expectOutput(addresses[0])) 77 s.assertCachedAddresses(c, addresses...) 78 79 // With --all, ensure all are returned. 80 s.runAndCheckOutput(c, "smart", expectOutput(addresses...), "--all") 81 s.assertCachedAddresses(c, addresses...) 82 } 83 84 // Ensure regardless of the prefer-ipv6 value we have the same 85 // result. 86 for i, envPreferIPv6 := range []bool{true, false} { 87 for j, bootPreferIPv6 := range []bool{true, false} { 88 testRun(i+j, envPreferIPv6, bootPreferIPv6) 89 } 90 } 91 } 92 93 func (s *EndpointSuite) TestRefresh(c *gc.C) { 94 testRun := func(i int, address network.HostPort, explicitRefresh bool) { 95 c.Logf("\ntest %d: address=%q, explicitRefresh=%v", i, address, explicitRefresh) 96 97 // Cache the address. 98 s.setCachedAPIAddresses(c, address) 99 s.assertCachedAddresses(c, address) 100 // Clear instance/state addresses to ensure only the cached 101 // one will be used. 102 s.setServerAPIAddresses(c) 103 104 // Ensure we get and cache the first address (i.e. no changes) 105 if explicitRefresh { 106 s.runAndCheckOutput(c, "smart", expectOutput(address), "--refresh") 107 } else { 108 s.runAndCheckOutput(c, "smart", expectOutput(address)) 109 } 110 s.assertCachedAddresses(c, address) 111 } 112 113 // Test both IPv4 and IPv6 endpoints separately, first with 114 // implicit refresh, then explicit. 115 for i, explicitRefresh := range []bool{true, false} { 116 for j, addr := range s.addressesWithAPIPort(c, "localhost", "::1") { 117 testRun(i+j, addr, explicitRefresh) 118 } 119 } 120 } 121 122 func (s *EndpointSuite) TestSortingAndFilteringBeforeCachingRespectsPreferIPv6(c *gc.C) { 123 // Set the instance/state addresses to a mix of IPv4 and IPv6 124 // addresses of all kinds. 125 addresses := s.addressesWithAPIPort(c, 126 // The following two are needed to actually connect to the 127 // test API server. 128 "127.0.0.1", 129 "::1", 130 // Other examples. 131 "192.0.0.0", 132 "2001:db8::1", 133 "169.254.1.2", // link-local - will be removed. 134 "fd00::1", 135 "ff01::1", // link-local - will be removed. 136 "fc00::1", 137 "localhost", 138 "0.1.2.3", 139 "127.0.1.1", // removed as a duplicate. 140 "::1", // removed as a duplicate. 141 "10.0.0.1", 142 "8.8.8.8", 143 ) 144 s.setServerAPIAddresses(c, addresses...) 145 146 // Clear cached the address to force a refresh. 147 s.setCachedAPIAddresses(c) 148 s.assertCachedAddresses(c) 149 // Set prefer-ipv6 to true first. 150 s.setPreferIPv6BootstrapConfig(c, true) 151 152 // Build the expected addresses list, after processing. 153 expectAddresses := s.addressesWithAPIPort(c, 154 "127.0.0.1", // This is always on top. 155 "2001:db8::1", 156 "0.1.2.3", 157 "192.0.0.0", 158 "8.8.8.8", 159 "localhost", 160 "fc00::1", 161 "fd00::1", 162 "10.0.0.1", 163 ) 164 s.runAndCheckOutput(c, "smart", expectOutput(expectAddresses...), "--all") 165 s.assertCachedAddresses(c, expectAddresses...) 166 167 // Now run it again with prefer-ipv6: false. 168 // But first reset the cached addresses.. 169 s.setCachedAPIAddresses(c) 170 s.assertCachedAddresses(c) 171 s.setPreferIPv6BootstrapConfig(c, false) 172 173 // Rebuild the expected addresses and rebuild them so IPv4 comes 174 // before IPv6. 175 expectAddresses = s.addressesWithAPIPort(c, 176 "127.0.0.1", // This is always on top. 177 "0.1.2.3", 178 "192.0.0.0", 179 "8.8.8.8", 180 "2001:db8::1", 181 "localhost", 182 "10.0.0.1", 183 "fc00::1", 184 "fd00::1", 185 ) 186 s.runAndCheckOutput(c, "smart", expectOutput(expectAddresses...), "--all") 187 s.assertCachedAddresses(c, expectAddresses...) 188 } 189 190 func (s *EndpointSuite) TestAllFormats(c *gc.C) { 191 addresses := s.addressesWithAPIPort(c, 192 "127.0.0.1", 193 "8.8.8.8", 194 "2001:db8::1", 195 "::1", 196 "10.0.0.1", 197 "fc00::1", 198 ) 199 s.setServerAPIAddresses(c) 200 s.setCachedAPIAddresses(c, addresses...) 201 s.assertCachedAddresses(c, addresses...) 202 203 for i, test := range []struct { 204 about string 205 args []string 206 format string 207 output []network.HostPort 208 }{{ 209 about: "default format (smart), no args", 210 format: "smart", 211 output: addresses[0:1], 212 }, { 213 about: "default format (smart), with --all", 214 args: []string{"--all"}, 215 format: "smart", 216 output: addresses, 217 }, { 218 about: "JSON format, without --all", 219 args: []string{"--format", "json"}, 220 format: "json", 221 output: addresses[0:1], 222 }, { 223 about: "JSON format, with --all", 224 args: []string{"--format", "json", "--all"}, 225 format: "json", 226 output: addresses, 227 }, { 228 about: "YAML format, without --all", 229 args: []string{"--format", "yaml"}, 230 format: "yaml", 231 output: addresses[0:1], 232 }, { 233 about: "YAML format, with --all", 234 args: []string{"--format", "yaml", "--all"}, 235 format: "yaml", 236 output: addresses, 237 }} { 238 c.Logf("\ntest %d: %s", i, test.about) 239 s.runAndCheckOutput(c, test.format, expectOutput(test.output...), test.args...) 240 } 241 } 242 243 // runCommand runs the api-endpoints command with the given arguments 244 // and returns the output and any error. 245 func (s *EndpointSuite) runCommand(c *gc.C, args ...string) (string, string, error) { 246 command := &EndpointCommand{} 247 ctx, err := coretesting.RunCommand(c, envcmd.Wrap(command), args...) 248 if err != nil { 249 return "", "", err 250 } 251 return coretesting.Stdout(ctx), coretesting.Stderr(ctx), nil 252 } 253 254 // runAndCheckOutput runs api-endpoints expecting no error and 255 // compares the output for the given format. 256 func (s *EndpointSuite) runAndCheckOutput(c *gc.C, format string, output []interface{}, args ...string) { 257 stdout, stderr, err := s.runCommand(c, args...) 258 if !c.Check(err, jc.ErrorIsNil) { 259 return 260 } 261 c.Check(stderr, gc.Equals, "") 262 switch format { 263 case "smart": 264 strOutput := "" 265 for _, line := range output { 266 strOutput += line.(string) + "\n" 267 } 268 c.Check(stdout, gc.Equals, strOutput) 269 case "json": 270 c.Check(stdout, jc.JSONEquals, output) 271 case "yaml": 272 c.Check(stdout, jc.YAMLEquals, output) 273 default: 274 c.Fatalf("unexpected format %q", format) 275 } 276 } 277 278 // getStoreInfo returns the current environment's EnvironInfo. 279 func (s *EndpointSuite) getStoreInfo(c *gc.C) configstore.EnvironInfo { 280 env, err := s.State.Environment() 281 c.Assert(err, jc.ErrorIsNil) 282 info, err := s.ConfigStore.ReadInfo(env.Name()) 283 c.Assert(err, jc.ErrorIsNil) 284 return info 285 } 286 287 // setPreferIPv6EnvironConfig sets the "prefer-ipv6" environment 288 // setting to given value. 289 func (s *EndpointSuite) setPreferIPv6EnvironConfig(c *gc.C, value bool) { 290 // Technically, because prefer-ipv6 is an immutable setting, what 291 // follows should be impossible, but the dummy provider doesn't 292 // seem to validate the new config against the current (old) one 293 // when calling SetConfig(). 294 allAttrs := s.Environ.Config().AllAttrs() 295 allAttrs["prefer-ipv6"] = value 296 cfg, err := config.New(config.NoDefaults, allAttrs) 297 c.Assert(err, jc.ErrorIsNil) 298 err = s.Environ.SetConfig(cfg) 299 c.Assert(err, jc.ErrorIsNil) 300 setValue := cfg.AllAttrs()["prefer-ipv6"].(bool) 301 c.Logf("environ config prefer-ipv6 set to %v", setValue) 302 } 303 304 // setPreferIPv6BootstrapConfig sets the "prefer-ipv6" setting to the 305 // given value on the current environment's bootstrap config by 306 // recreating it (the only way to change bootstrap config once set). 307 func (s *EndpointSuite) setPreferIPv6BootstrapConfig(c *gc.C, value bool) { 308 currentInfo := s.getStoreInfo(c) 309 endpoint := currentInfo.APIEndpoint() 310 creds := currentInfo.APICredentials() 311 bootstrapConfig := currentInfo.BootstrapConfig() 312 delete(bootstrapConfig, "prefer-ipv6") 313 314 // The only way to change the bootstrap config is to recreate the 315 // info. 316 err := currentInfo.Destroy() 317 c.Assert(err, jc.ErrorIsNil) 318 newInfo := s.ConfigStore.CreateInfo(s.Environ.Config().Name()) 319 newInfo.SetAPICredentials(creds) 320 newInfo.SetAPIEndpoint(endpoint) 321 newCfg := make(coretesting.Attrs) 322 newCfg["prefer-ipv6"] = value 323 newInfo.SetBootstrapConfig(newCfg.Merge(bootstrapConfig)) 324 err = newInfo.Write() 325 c.Assert(err, jc.ErrorIsNil) 326 setValue := newInfo.BootstrapConfig()["prefer-ipv6"].(bool) 327 c.Logf("bootstrap config prefer-ipv6 set to %v", setValue) 328 } 329 330 // setCachedAPIAddresses sets the given addresses on the cached 331 // EnvironInfo endpoint. APIEndpoint.Hostnames are not touched, 332 // because the interactions between Addresses and Hostnames are 333 // separately tested in juju/api_test.go 334 func (s *EndpointSuite) setCachedAPIAddresses(c *gc.C, addresses ...network.HostPort) { 335 info := s.getStoreInfo(c) 336 endpoint := info.APIEndpoint() 337 endpoint.Addresses = network.HostPortsToStrings(addresses) 338 info.SetAPIEndpoint(endpoint) 339 err := info.Write() 340 c.Assert(err, jc.ErrorIsNil) 341 c.Logf("cached addresses set to %v", info.APIEndpoint().Addresses) 342 } 343 344 // setServerAPIAddresses sets the given addresses on the dummy 345 // bootstrap instance and in state. 346 func (s *EndpointSuite) setServerAPIAddresses(c *gc.C, addresses ...network.HostPort) { 347 insts, err := s.Environ.Instances([]instance.Id{dummy.BootstrapInstanceId}) 348 c.Assert(err, jc.ErrorIsNil) 349 err = s.State.SetAPIHostPorts([][]network.HostPort{addresses}) 350 c.Assert(err, jc.ErrorIsNil) 351 dummy.SetInstanceAddresses(insts[0], network.HostsWithoutPort(addresses)) 352 instAddrs, err := insts[0].Addresses() 353 c.Assert(err, jc.ErrorIsNil) 354 stateAddrs, err := s.State.APIHostPorts() 355 c.Assert(err, jc.ErrorIsNil) 356 c.Logf("instance addresses set to %v", instAddrs) 357 c.Logf("state addresses set to %v", stateAddrs) 358 } 359 360 // addressesWithAPIPort returns the given addresses appending the test 361 // API server listening port to each one. 362 func (s *EndpointSuite) addressesWithAPIPort(c *gc.C, addresses ...string) []network.HostPort { 363 apiPort := s.Environ.Config().APIPort() 364 return network.NewHostPorts(apiPort, addresses...) 365 } 366 367 // assertCachedAddresses ensures the endpoint addresses (not 368 // hostnames) stored in the store match the given ones. 369 // APIEndpoint.Hostnames and APIEndpoint.Addresses interactions are 370 // separately testing in juju/api_test.go. 371 func (s *EndpointSuite) assertCachedAddresses(c *gc.C, addresses ...network.HostPort) { 372 info := s.getStoreInfo(c) 373 strAddresses := network.HostPortsToStrings(addresses) 374 c.Assert(info.APIEndpoint().Addresses, jc.DeepEquals, strAddresses) 375 } 376 377 // expectOutput is a helper used to construct the expected ouput 378 // argument to runAndCheckOutput. 379 func expectOutput(addresses ...network.HostPort) []interface{} { 380 result := make([]interface{}, len(addresses)) 381 for i, addr := range addresses { 382 result[i] = addr.NetAddr() 383 } 384 return result 385 }