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  }