github.com/Pankov404/juju@v0.0.0-20150703034450-be266991dceb/juju/api_test.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package juju_test
     5  
     6  import (
     7  	"fmt"
     8  	"net"
     9  	"os"
    10  	"strconv"
    11  	"strings"
    12  	"time"
    13  
    14  	"github.com/juju/errors"
    15  	"github.com/juju/names"
    16  	"github.com/juju/testing"
    17  	jc "github.com/juju/testing/checkers"
    18  	gc "gopkg.in/check.v1"
    19  
    20  	"github.com/juju/juju/api"
    21  	"github.com/juju/juju/environs"
    22  	"github.com/juju/juju/environs/bootstrap"
    23  	"github.com/juju/juju/environs/config"
    24  	"github.com/juju/juju/environs/configstore"
    25  	"github.com/juju/juju/environs/filestorage"
    26  	envtesting "github.com/juju/juju/environs/testing"
    27  	envtools "github.com/juju/juju/environs/tools"
    28  	"github.com/juju/juju/juju"
    29  	"github.com/juju/juju/juju/osenv"
    30  	jujutesting "github.com/juju/juju/juju/testing"
    31  	"github.com/juju/juju/network"
    32  	"github.com/juju/juju/provider/dummy"
    33  	coretesting "github.com/juju/juju/testing"
    34  )
    35  
    36  type NewAPIStateSuite struct {
    37  	coretesting.FakeJujuHomeSuite
    38  	testing.MgoSuite
    39  	envtesting.ToolsFixture
    40  }
    41  
    42  var _ = gc.Suite(&NewAPIStateSuite{})
    43  
    44  func (cs *NewAPIStateSuite) SetUpSuite(c *gc.C) {
    45  	cs.FakeJujuHomeSuite.SetUpSuite(c)
    46  	cs.MgoSuite.SetUpSuite(c)
    47  }
    48  
    49  func (cs *NewAPIStateSuite) TearDownSuite(c *gc.C) {
    50  	cs.MgoSuite.TearDownSuite(c)
    51  	cs.FakeJujuHomeSuite.TearDownSuite(c)
    52  }
    53  
    54  func (cs *NewAPIStateSuite) SetUpTest(c *gc.C) {
    55  	cs.FakeJujuHomeSuite.SetUpTest(c)
    56  	cs.MgoSuite.SetUpTest(c)
    57  	cs.ToolsFixture.SetUpTest(c)
    58  }
    59  
    60  func (cs *NewAPIStateSuite) TearDownTest(c *gc.C) {
    61  	dummy.Reset()
    62  	cs.ToolsFixture.TearDownTest(c)
    63  	cs.MgoSuite.TearDownTest(c)
    64  	cs.FakeJujuHomeSuite.TearDownTest(c)
    65  }
    66  
    67  func (cs *NewAPIStateSuite) TestNewAPIState(c *gc.C) {
    68  	cfg, err := config.New(config.NoDefaults, dummy.SampleConfig())
    69  	c.Assert(err, jc.ErrorIsNil)
    70  	ctx := envtesting.BootstrapContext(c)
    71  	env, err := environs.Prepare(cfg, ctx, configstore.NewMem())
    72  	c.Assert(err, jc.ErrorIsNil)
    73  
    74  	storageDir := c.MkDir()
    75  	cs.PatchValue(&envtools.DefaultBaseURL, storageDir)
    76  	stor, err := filestorage.NewFileStorageWriter(storageDir)
    77  	c.Assert(err, jc.ErrorIsNil)
    78  	envtesting.UploadFakeTools(c, stor, "released", "released")
    79  
    80  	err = bootstrap.Bootstrap(ctx, env, bootstrap.BootstrapParams{})
    81  	c.Assert(err, jc.ErrorIsNil)
    82  
    83  	cfg = env.Config()
    84  	cfg, err = cfg.Apply(map[string]interface{}{
    85  		"secret": "fnord",
    86  	})
    87  	c.Assert(err, jc.ErrorIsNil)
    88  	err = env.SetConfig(cfg)
    89  	c.Assert(err, jc.ErrorIsNil)
    90  
    91  	st, err := juju.NewAPIState(dummy.AdminUserTag(), env, api.DialOpts{})
    92  	c.Assert(st, gc.NotNil)
    93  
    94  	// the secrets will not be updated, as they already exist
    95  	attrs, err := st.Client().EnvironmentGet()
    96  	c.Assert(attrs["secret"], gc.Equals, "pork")
    97  
    98  	c.Assert(st.Close(), gc.IsNil)
    99  }
   100  
   101  type NewAPIClientSuite struct {
   102  	coretesting.FakeJujuHomeSuite
   103  	testing.MgoSuite
   104  	envtesting.ToolsFixture
   105  }
   106  
   107  var _ = gc.Suite(&NewAPIClientSuite{})
   108  
   109  func (cs *NewAPIClientSuite) SetUpSuite(c *gc.C) {
   110  	cs.FakeJujuHomeSuite.SetUpSuite(c)
   111  	cs.MgoSuite.SetUpSuite(c)
   112  	// Since most tests use invalid testing API server addresses, we
   113  	// need to mock this to avoid errors.
   114  	cs.PatchValue(juju.ServerAddress, func(addr string) (network.HostPort, error) {
   115  		host, strPort, err := net.SplitHostPort(addr)
   116  		if err != nil {
   117  			c.Logf("serverAddress %q invalid, ignoring error: %v", addr, err)
   118  		}
   119  		port, err := strconv.Atoi(strPort)
   120  		if err != nil {
   121  			c.Logf("serverAddress %q port, ignoring error: %v", addr, err)
   122  			port = 0
   123  		}
   124  		return network.NewHostPorts(port, host)[0], nil
   125  	})
   126  }
   127  
   128  func (cs *NewAPIClientSuite) TearDownSuite(c *gc.C) {
   129  	cs.MgoSuite.TearDownSuite(c)
   130  	cs.FakeJujuHomeSuite.TearDownSuite(c)
   131  }
   132  
   133  func (cs *NewAPIClientSuite) SetUpTest(c *gc.C) {
   134  	cs.ToolsFixture.SetUpTest(c)
   135  	cs.FakeJujuHomeSuite.SetUpTest(c)
   136  	cs.MgoSuite.SetUpTest(c)
   137  }
   138  
   139  func (cs *NewAPIClientSuite) TearDownTest(c *gc.C) {
   140  	dummy.Reset()
   141  	cs.ToolsFixture.TearDownTest(c)
   142  	cs.MgoSuite.TearDownTest(c)
   143  	cs.FakeJujuHomeSuite.TearDownTest(c)
   144  }
   145  
   146  func (s *NewAPIClientSuite) bootstrapEnv(c *gc.C, envName string, store configstore.Storage) {
   147  	if store == nil {
   148  		store = configstore.NewMem()
   149  	}
   150  	ctx := envtesting.BootstrapContext(c)
   151  	c.Logf("env name: %s", envName)
   152  	env, err := environs.PrepareFromName(envName, ctx, store)
   153  	c.Assert(err, jc.ErrorIsNil)
   154  
   155  	storageDir := c.MkDir()
   156  	s.PatchValue(&envtools.DefaultBaseURL, storageDir)
   157  	stor, err := filestorage.NewFileStorageWriter(storageDir)
   158  	c.Assert(err, jc.ErrorIsNil)
   159  	envtesting.UploadFakeTools(c, stor, "released", "released")
   160  
   161  	err = bootstrap.Bootstrap(ctx, env, bootstrap.BootstrapParams{})
   162  	c.Assert(err, jc.ErrorIsNil)
   163  	info, err := store.ReadInfo(envName)
   164  	c.Assert(err, jc.ErrorIsNil)
   165  	creds := info.APICredentials()
   166  	creds.User = dummy.AdminUserTag().Name()
   167  	c.Logf("set creds: %#v", creds)
   168  	info.SetAPICredentials(creds)
   169  	err = info.Write()
   170  	c.Assert(err, jc.ErrorIsNil)
   171  	c.Logf("creds: %#v", info.APICredentials())
   172  	info, err = store.ReadInfo(envName)
   173  	c.Assert(err, jc.ErrorIsNil)
   174  	c.Logf("read creds: %#v", info.APICredentials())
   175  	c.Logf("store: %#v", store)
   176  }
   177  
   178  func (s *NewAPIClientSuite) TestNameDefault(c *gc.C) {
   179  	coretesting.WriteEnvironments(c, coretesting.MultipleEnvConfig)
   180  	// The connection logic should not delay the config connection
   181  	// at all when there is no environment info available.
   182  	// Make sure of that by providing a suitably long delay
   183  	// and checking that the connection happens within that
   184  	// time.
   185  	s.PatchValue(juju.ProviderConnectDelay, coretesting.LongWait)
   186  	s.bootstrapEnv(c, coretesting.SampleEnvName, defaultConfigStore(c))
   187  
   188  	startTime := time.Now()
   189  	apiclient, err := juju.NewAPIClientFromName("")
   190  	c.Assert(err, jc.ErrorIsNil)
   191  	defer apiclient.Close()
   192  	c.Assert(time.Since(startTime), jc.LessThan, coretesting.LongWait)
   193  
   194  	// We should get the default sample environment if we ask for ""
   195  	assertEnvironmentName(c, apiclient, coretesting.SampleEnvName)
   196  }
   197  
   198  func (s *NewAPIClientSuite) TestNameNotDefault(c *gc.C) {
   199  	envName := coretesting.SampleCertName + "-2"
   200  	coretesting.WriteEnvironments(c, coretesting.MultipleEnvConfig, envName)
   201  	s.bootstrapEnv(c, envName, defaultConfigStore(c))
   202  	apiclient, err := juju.NewAPIClientFromName(envName)
   203  	c.Assert(err, jc.ErrorIsNil)
   204  	defer apiclient.Close()
   205  	assertEnvironmentName(c, apiclient, envName)
   206  }
   207  
   208  func (s *NewAPIClientSuite) TestWithInfoOnly(c *gc.C) {
   209  	store := newConfigStore("noconfig", dummyStoreInfo)
   210  
   211  	called := 0
   212  	expectState := mockedAPIState(mockedHostPort | mockedEnvironTag)
   213  	apiOpen := func(apiInfo *api.Info, opts api.DialOpts) (juju.APIState, error) {
   214  		checkCommonAPIInfoAttrs(c, apiInfo, opts)
   215  		c.Check(apiInfo.EnvironTag, gc.Equals, names.NewEnvironTag(fakeUUID))
   216  		called++
   217  		return expectState, nil
   218  	}
   219  
   220  	// Give NewAPIFromStore a store interface that can report when the
   221  	// config was written to, to check if the cache is updated.
   222  	mockStore := &storageWithWriteNotify{store: store}
   223  	st, err := juju.NewAPIFromStore("noconfig", mockStore, apiOpen)
   224  	c.Assert(err, jc.ErrorIsNil)
   225  	c.Assert(st, gc.Equals, expectState)
   226  	c.Assert(called, gc.Equals, 1)
   227  	c.Assert(mockStore.written, jc.IsTrue)
   228  	info, err := store.ReadInfo("noconfig")
   229  	c.Assert(err, jc.ErrorIsNil)
   230  	ep := info.APIEndpoint()
   231  	c.Check(ep.Addresses, jc.DeepEquals, []string{
   232  		"0.1.2.3:1234", "[2001:db8::1]:1234",
   233  	})
   234  	c.Check(ep.EnvironUUID, gc.Equals, fakeUUID)
   235  	mockStore.written = false
   236  
   237  	// If APIHostPorts haven't changed, then the store won't be updated.
   238  	st, err = juju.NewAPIFromStore("noconfig", mockStore, apiOpen)
   239  	c.Assert(err, jc.ErrorIsNil)
   240  	c.Assert(st, gc.Equals, expectState)
   241  	c.Assert(called, gc.Equals, 2)
   242  	c.Assert(mockStore.written, jc.IsFalse)
   243  }
   244  
   245  func (s *NewAPIClientSuite) TestWithConfigAndNoInfo(c *gc.C) {
   246  	c.Skip("not really possible now that there is no defined admin user")
   247  	coretesting.MakeSampleJujuHome(c)
   248  
   249  	store := newConfigStore(coretesting.SampleEnvName, &environInfo{
   250  		bootstrapConfig: map[string]interface{}{
   251  			"type":                      "dummy",
   252  			"name":                      "myenv",
   253  			"state-server":              true,
   254  			"authorized-keys":           "i-am-a-key",
   255  			"default-series":            config.LatestLtsSeries(),
   256  			"firewall-mode":             config.FwInstance,
   257  			"development":               false,
   258  			"ssl-hostname-verification": true,
   259  			"admin-secret":              "adminpass",
   260  		},
   261  	})
   262  	s.bootstrapEnv(c, coretesting.SampleEnvName, store)
   263  
   264  	info, err := store.ReadInfo("myenv")
   265  	c.Assert(err, jc.ErrorIsNil)
   266  	c.Assert(info, gc.NotNil)
   267  	c.Logf("%#v", info.APICredentials())
   268  
   269  	called := 0
   270  	expectState := mockedAPIState(0)
   271  	apiOpen := func(apiInfo *api.Info, opts api.DialOpts) (juju.APIState, error) {
   272  		c.Check(apiInfo.Tag, gc.Equals, dummy.AdminUserTag())
   273  		c.Check(string(apiInfo.CACert), gc.Not(gc.Equals), "")
   274  		c.Check(apiInfo.Password, gc.Equals, "adminpass")
   275  		// EnvironTag wasn't in regular Config
   276  		c.Check(apiInfo.EnvironTag.Id(), gc.Equals, "")
   277  		c.Check(opts, gc.DeepEquals, api.DefaultDialOpts())
   278  		called++
   279  		return expectState, nil
   280  	}
   281  	st, err := juju.NewAPIFromStore("myenv", store, apiOpen)
   282  	c.Assert(err, jc.ErrorIsNil)
   283  	c.Assert(st, gc.Equals, expectState)
   284  	c.Assert(called, gc.Equals, 1)
   285  
   286  	// Make sure the cache is updated.
   287  	info, err = store.ReadInfo("myenv")
   288  	c.Assert(err, jc.ErrorIsNil)
   289  	c.Assert(info, gc.NotNil)
   290  	ep := info.APIEndpoint()
   291  	c.Assert(ep.Addresses, gc.HasLen, 1)
   292  	c.Check(ep.Addresses[0], gc.Matches, `localhost:\d+`)
   293  	c.Check(ep.CACert, gc.Not(gc.Equals), "")
   294  }
   295  
   296  func (s *NewAPIClientSuite) TestWithInfoError(c *gc.C) {
   297  	expectErr := fmt.Errorf("an error")
   298  	store := newConfigStoreWithError(expectErr)
   299  	client, err := juju.NewAPIFromStore("noconfig", store, panicAPIOpen)
   300  	c.Assert(err, gc.Equals, expectErr)
   301  	c.Assert(client, gc.IsNil)
   302  }
   303  
   304  func (s *NewAPIClientSuite) TestWithInfoNoAddresses(c *gc.C) {
   305  	store := newConfigStore("noconfig", &environInfo{
   306  		endpoint: configstore.APIEndpoint{
   307  			Addresses: []string{},
   308  			CACert:    "certificated",
   309  		},
   310  	})
   311  	st, err := juju.NewAPIFromStore("noconfig", store, panicAPIOpen)
   312  	c.Assert(err, gc.ErrorMatches, `environment "noconfig" not found`)
   313  	c.Assert(st, gc.IsNil)
   314  }
   315  
   316  var noTagStoreInfo = &environInfo{
   317  	creds: configstore.APICredentials{
   318  		User:     "foo",
   319  		Password: "foopass",
   320  	},
   321  	endpoint: configstore.APIEndpoint{
   322  		Addresses: []string{"foo.invalid"},
   323  		CACert:    "certificated",
   324  	},
   325  }
   326  
   327  type mockedStateFlags int
   328  
   329  const (
   330  	noFlags          mockedStateFlags = 0x0000
   331  	mockedHostPort   mockedStateFlags = 0x0001
   332  	mockedEnvironTag mockedStateFlags = 0x0002
   333  	mockedPreferIPv6 mockedStateFlags = 0x0004
   334  )
   335  
   336  func mockedAPIState(flags mockedStateFlags) *mockAPIState {
   337  	hasHostPort := flags&mockedHostPort == mockedHostPort
   338  	hasEnvironTag := flags&mockedEnvironTag == mockedEnvironTag
   339  	preferIPv6 := flags&mockedPreferIPv6 == mockedPreferIPv6
   340  	addr := ""
   341  
   342  	apiHostPorts := [][]network.HostPort{}
   343  	if hasHostPort {
   344  		var apiAddrs []network.Address
   345  		ipv4Address := network.NewAddress("0.1.2.3")
   346  		ipv6Address := network.NewAddress("2001:db8::1")
   347  		if preferIPv6 {
   348  			addr = net.JoinHostPort(ipv6Address.Value, "1234")
   349  			apiAddrs = append(apiAddrs, ipv6Address, ipv4Address)
   350  		} else {
   351  			addr = net.JoinHostPort(ipv4Address.Value, "1234")
   352  			apiAddrs = append(apiAddrs, ipv4Address, ipv6Address)
   353  		}
   354  		apiHostPorts = [][]network.HostPort{
   355  			network.AddressesWithPort(apiAddrs, 1234),
   356  		}
   357  	}
   358  	environTag := ""
   359  	if hasEnvironTag {
   360  		environTag = "environment-df136476-12e9-11e4-8a70-b2227cce2b54"
   361  	}
   362  	return &mockAPIState{
   363  		apiHostPorts: apiHostPorts,
   364  		environTag:   environTag,
   365  		addr:         addr,
   366  	}
   367  }
   368  
   369  func checkCommonAPIInfoAttrs(c *gc.C, apiInfo *api.Info, opts api.DialOpts) {
   370  	c.Check(apiInfo.Tag, gc.Equals, names.NewUserTag("foo"))
   371  	c.Check(string(apiInfo.CACert), gc.Equals, "certificated")
   372  	c.Check(apiInfo.Password, gc.Equals, "foopass")
   373  	c.Check(opts, gc.DeepEquals, api.DefaultDialOpts())
   374  }
   375  
   376  func (s *NewAPIClientSuite) TestWithInfoNoEnvironTag(c *gc.C) {
   377  	store := newConfigStore("noconfig", noTagStoreInfo)
   378  
   379  	called := 0
   380  	expectState := mockedAPIState(mockedHostPort | mockedEnvironTag)
   381  	apiOpen := func(apiInfo *api.Info, opts api.DialOpts) (juju.APIState, error) {
   382  		checkCommonAPIInfoAttrs(c, apiInfo, opts)
   383  		c.Check(apiInfo.EnvironTag.Id(), gc.Equals, "")
   384  		called++
   385  		return expectState, nil
   386  	}
   387  
   388  	// Give NewAPIFromStore a store interface that can report when the
   389  	// config was written to, to check if the cache is updated.
   390  	mockStore := &storageWithWriteNotify{store: store}
   391  	st, err := juju.NewAPIFromStore("noconfig", mockStore, apiOpen)
   392  	c.Assert(err, jc.ErrorIsNil)
   393  	c.Assert(st, gc.Equals, expectState)
   394  	c.Assert(called, gc.Equals, 1)
   395  	c.Assert(mockStore.written, jc.IsTrue)
   396  	info, err := store.ReadInfo("noconfig")
   397  	c.Assert(err, jc.ErrorIsNil)
   398  	c.Check(info.APIEndpoint().Addresses, jc.DeepEquals, []string{
   399  		"0.1.2.3:1234", "[2001:db8::1]:1234",
   400  	})
   401  	c.Check(info.APIEndpoint().EnvironUUID, gc.Equals, fakeUUID)
   402  
   403  	// Now simulate prefer-ipv6: true
   404  	store = newConfigStore("noconfig", noTagStoreInfo)
   405  	mockStore = &storageWithWriteNotify{store: store}
   406  	s.PatchValue(juju.MaybePreferIPv6, func(_ configstore.EnvironInfo) bool {
   407  		return true
   408  	})
   409  	expectState = mockedAPIState(mockedHostPort | mockedEnvironTag | mockedPreferIPv6)
   410  	st, err = juju.NewAPIFromStore("noconfig", mockStore, apiOpen)
   411  	c.Assert(err, jc.ErrorIsNil)
   412  	c.Assert(st, gc.Equals, expectState)
   413  	c.Assert(called, gc.Equals, 2)
   414  	c.Assert(mockStore.written, jc.IsTrue)
   415  	info, err = store.ReadInfo("noconfig")
   416  	c.Assert(err, jc.ErrorIsNil)
   417  	c.Check(info.APIEndpoint().Addresses, jc.DeepEquals, []string{
   418  		"[2001:db8::1]:1234", "0.1.2.3:1234",
   419  	})
   420  	c.Check(info.APIEndpoint().EnvironUUID, gc.Equals, fakeUUID)
   421  }
   422  
   423  func (s *NewAPIClientSuite) TestWithInfoNoAPIHostports(c *gc.C) {
   424  	// The local cache doesn't have an EnvironTag, which the API does
   425  	// return. However, the API doesn't have apiHostPorts, we don't want to
   426  	// override the local cache with bad endpoints.
   427  	store := newConfigStore("noconfig", noTagStoreInfo)
   428  
   429  	called := 0
   430  	expectState := mockedAPIState(mockedEnvironTag | mockedPreferIPv6)
   431  	apiOpen := func(apiInfo *api.Info, opts api.DialOpts) (juju.APIState, error) {
   432  		checkCommonAPIInfoAttrs(c, apiInfo, opts)
   433  		c.Check(apiInfo.EnvironTag.Id(), gc.Equals, "")
   434  		called++
   435  		return expectState, nil
   436  	}
   437  
   438  	mockStore := &storageWithWriteNotify{store: store}
   439  	st, err := juju.NewAPIFromStore("noconfig", mockStore, apiOpen)
   440  	c.Assert(err, jc.ErrorIsNil)
   441  	c.Assert(st, gc.Equals, expectState)
   442  	c.Assert(called, gc.Equals, 1)
   443  	c.Assert(mockStore.written, jc.IsTrue)
   444  	info, err := store.ReadInfo("noconfig")
   445  	c.Assert(err, jc.ErrorIsNil)
   446  	ep := info.APIEndpoint()
   447  	// We should have cached the environ tag, but not disturbed the
   448  	// Addresses
   449  	c.Check(ep.Addresses, gc.HasLen, 1)
   450  	c.Check(ep.Addresses[0], gc.Matches, `foo\.invalid`)
   451  	c.Check(ep.EnvironUUID, gc.Equals, fakeUUID)
   452  }
   453  
   454  func (s *NewAPIClientSuite) TestNoEnvironTagDoesntOverwriteCached(c *gc.C) {
   455  	store := newConfigStore("noconfig", dummyStoreInfo)
   456  	called := 0
   457  	// State returns a new set of APIHostPorts but not a new EnvironTag. We
   458  	// shouldn't override the cached value with environ tag of "".
   459  	expectState := mockedAPIState(mockedHostPort)
   460  	apiOpen := func(apiInfo *api.Info, opts api.DialOpts) (juju.APIState, error) {
   461  		checkCommonAPIInfoAttrs(c, apiInfo, opts)
   462  		c.Check(apiInfo.EnvironTag, gc.Equals, names.NewEnvironTag(fakeUUID))
   463  		called++
   464  		return expectState, nil
   465  	}
   466  
   467  	mockStore := &storageWithWriteNotify{store: store}
   468  	st, err := juju.NewAPIFromStore("noconfig", mockStore, apiOpen)
   469  	c.Assert(err, jc.ErrorIsNil)
   470  	c.Assert(st, gc.Equals, expectState)
   471  	c.Assert(called, gc.Equals, 1)
   472  	c.Assert(mockStore.written, jc.IsTrue)
   473  	info, err := store.ReadInfo("noconfig")
   474  	c.Assert(err, jc.ErrorIsNil)
   475  	ep := info.APIEndpoint()
   476  	c.Check(ep.Addresses, gc.DeepEquals, []string{
   477  		"0.1.2.3:1234", "[2001:db8::1]:1234",
   478  	})
   479  	c.Check(ep.EnvironUUID, gc.Equals, fakeUUID)
   480  
   481  	// Now simulate prefer-ipv6: true
   482  	s.PatchValue(juju.MaybePreferIPv6, func(_ configstore.EnvironInfo) bool {
   483  		return true
   484  	})
   485  	expectState = mockedAPIState(mockedHostPort | mockedPreferIPv6)
   486  	st, err = juju.NewAPIFromStore("noconfig", mockStore, apiOpen)
   487  	c.Assert(err, jc.ErrorIsNil)
   488  	c.Assert(st, gc.Equals, expectState)
   489  	c.Assert(called, gc.Equals, 2)
   490  	c.Assert(mockStore.written, jc.IsTrue)
   491  	info, err = store.ReadInfo("noconfig")
   492  	c.Assert(err, jc.ErrorIsNil)
   493  	ep = info.APIEndpoint()
   494  	c.Check(ep.Addresses, gc.DeepEquals, []string{
   495  		"[2001:db8::1]:1234", "0.1.2.3:1234",
   496  	})
   497  	c.Check(ep.EnvironUUID, gc.Equals, fakeUUID)
   498  }
   499  
   500  func (s *NewAPIClientSuite) TestWithInfoAPIOpenError(c *gc.C) {
   501  	store := newConfigStore("noconfig", &environInfo{
   502  		endpoint: configstore.APIEndpoint{
   503  			Addresses: []string{"foo.invalid"},
   504  		},
   505  	})
   506  
   507  	apiOpen := func(apiInfo *api.Info, opts api.DialOpts) (juju.APIState, error) {
   508  		return nil, errors.Errorf("an error")
   509  	}
   510  	st, err := juju.NewAPIFromStore("noconfig", store, apiOpen)
   511  	// We expect to  get the isNotFound error as it is more important than the
   512  	// infoConnectError "an error"
   513  	c.Assert(err, gc.ErrorMatches, "environment \"noconfig\" not found")
   514  	c.Assert(st, gc.IsNil)
   515  }
   516  
   517  func (s *NewAPIClientSuite) TestWithSlowInfoConnect(c *gc.C) {
   518  	coretesting.MakeSampleJujuHome(c)
   519  	store := configstore.NewMem()
   520  	s.bootstrapEnv(c, coretesting.SampleEnvName, store)
   521  	setEndpointAddressAndHostname(c, store, coretesting.SampleEnvName, "0.1.2.3", "infoapi.invalid")
   522  
   523  	infoOpenedState := mockedAPIState(noFlags)
   524  	infoEndpointOpened := make(chan struct{})
   525  	cfgOpenedState := mockedAPIState(noFlags)
   526  	// On a sample run with no delay, the logic took 45ms to run, so
   527  	// we make the delay slightly more than that, so that if the
   528  	// logic doesn't delay at all, the test will fail reasonably consistently.
   529  	s.PatchValue(juju.ProviderConnectDelay, 50*time.Millisecond)
   530  	apiOpen := func(info *api.Info, opts api.DialOpts) (juju.APIState, error) {
   531  		if info.Addrs[0] == "0.1.2.3" {
   532  			infoEndpointOpened <- struct{}{}
   533  			return infoOpenedState, nil
   534  		}
   535  		return cfgOpenedState, nil
   536  	}
   537  
   538  	stateClosed := make(chan juju.APIState)
   539  	infoOpenedState.close = func(st juju.APIState) error {
   540  		stateClosed <- st
   541  		return nil
   542  	}
   543  	cfgOpenedState.close = infoOpenedState.close
   544  
   545  	startTime := time.Now()
   546  	st, err := juju.NewAPIFromStore(coretesting.SampleEnvName, store, apiOpen)
   547  	c.Assert(err, jc.ErrorIsNil)
   548  	// The connection logic should wait for some time before opening
   549  	// the API from the configuration.
   550  	c.Assert(time.Since(startTime), jc.GreaterThan, *juju.ProviderConnectDelay)
   551  	c.Assert(st, gc.Equals, cfgOpenedState)
   552  
   553  	select {
   554  	case <-infoEndpointOpened:
   555  	case <-time.After(coretesting.LongWait):
   556  		c.Errorf("api never opened via info")
   557  	}
   558  
   559  	// Check that the ignored state was closed.
   560  	select {
   561  	case st := <-stateClosed:
   562  		c.Assert(st, gc.Equals, infoOpenedState)
   563  	case <-time.After(coretesting.LongWait):
   564  		c.Errorf("timed out waiting for state to be closed")
   565  	}
   566  }
   567  
   568  type badBootstrapInfo struct {
   569  	configstore.EnvironInfo
   570  }
   571  
   572  // BootstrapConfig is returned as a map with real content, but the content
   573  // isn't actually valid configuration, causing config.New to fail
   574  func (m *badBootstrapInfo) BootstrapConfig() map[string]interface{} {
   575  	return map[string]interface{}{"something": "else"}
   576  }
   577  
   578  func (s *NewAPIClientSuite) TestBadConfigDoesntPanic(c *gc.C) {
   579  	badInfo := &badBootstrapInfo{}
   580  	cfg, err := juju.GetConfig(badInfo, nil, "test")
   581  	// The specific error we get depends on what key is invalid, which is a
   582  	// bit spurious, but what we care about is that we didn't get a panic,
   583  	// but instead got an error
   584  	c.Assert(err, gc.ErrorMatches, ".*expected.*got nothing")
   585  	c.Assert(cfg, gc.IsNil)
   586  }
   587  
   588  func setEndpointAddressAndHostname(c *gc.C, store configstore.Storage, envName string, addr, host string) {
   589  	// Populate the environment's info with an endpoint
   590  	// with a known address and hostname.
   591  	info, err := store.ReadInfo(coretesting.SampleEnvName)
   592  	c.Assert(err, jc.ErrorIsNil)
   593  	info.SetAPIEndpoint(configstore.APIEndpoint{
   594  		Addresses: []string{addr},
   595  		Hostnames: []string{host},
   596  		CACert:    "certificated",
   597  	})
   598  	err = info.Write()
   599  	c.Assert(err, jc.ErrorIsNil)
   600  }
   601  
   602  func (s *NewAPIClientSuite) TestWithSlowConfigConnect(c *gc.C) {
   603  	coretesting.MakeSampleJujuHome(c)
   604  
   605  	store := configstore.NewMem()
   606  	s.bootstrapEnv(c, coretesting.SampleEnvName, store)
   607  	setEndpointAddressAndHostname(c, store, coretesting.SampleEnvName, "0.1.2.3", "infoapi.invalid")
   608  
   609  	infoOpenedState := mockedAPIState(noFlags)
   610  	infoEndpointOpened := make(chan struct{})
   611  	cfgOpenedState := mockedAPIState(noFlags)
   612  	cfgEndpointOpened := make(chan struct{})
   613  
   614  	s.PatchValue(juju.ProviderConnectDelay, 0*time.Second)
   615  	apiOpen := func(info *api.Info, opts api.DialOpts) (juju.APIState, error) {
   616  		if info.Addrs[0] == "0.1.2.3" {
   617  			infoEndpointOpened <- struct{}{}
   618  			<-infoEndpointOpened
   619  			return infoOpenedState, nil
   620  		}
   621  		cfgEndpointOpened <- struct{}{}
   622  		<-cfgEndpointOpened
   623  		return cfgOpenedState, nil
   624  	}
   625  
   626  	stateClosed := make(chan juju.APIState)
   627  	infoOpenedState.close = func(st juju.APIState) error {
   628  		stateClosed <- st
   629  		return nil
   630  	}
   631  	cfgOpenedState.close = infoOpenedState.close
   632  
   633  	done := make(chan struct{})
   634  	go func() {
   635  		st, err := juju.NewAPIFromStore(coretesting.SampleEnvName, store, apiOpen)
   636  		c.Check(err, jc.ErrorIsNil)
   637  		c.Check(st, gc.Equals, infoOpenedState)
   638  		close(done)
   639  	}()
   640  
   641  	// Check that we're trying to connect to both endpoints:
   642  	select {
   643  	case <-infoEndpointOpened:
   644  	case <-time.After(coretesting.LongWait):
   645  		c.Fatalf("api never opened via info")
   646  	}
   647  	select {
   648  	case <-cfgEndpointOpened:
   649  	case <-time.After(coretesting.LongWait):
   650  		c.Fatalf("api never opened via config")
   651  	}
   652  	// Let the info endpoint open go ahead and
   653  	// check that the NewAPIFromStore call returns.
   654  	infoEndpointOpened <- struct{}{}
   655  	select {
   656  	case <-done:
   657  	case <-time.After(coretesting.LongWait):
   658  		c.Errorf("timed out opening API")
   659  	}
   660  
   661  	// Let the config endpoint open go ahead and
   662  	// check that its state is closed.
   663  	cfgEndpointOpened <- struct{}{}
   664  	select {
   665  	case st := <-stateClosed:
   666  		c.Assert(st, gc.Equals, cfgOpenedState)
   667  	case <-time.After(coretesting.LongWait):
   668  		c.Errorf("timed out waiting for state to be closed")
   669  	}
   670  }
   671  
   672  func (s *NewAPIClientSuite) TestBothError(c *gc.C) {
   673  	coretesting.MakeSampleJujuHome(c)
   674  	store := configstore.NewMem()
   675  	s.bootstrapEnv(c, coretesting.SampleEnvName, store)
   676  	setEndpointAddressAndHostname(c, store, coretesting.SampleEnvName, "0.1.2.3", "infoapi.invalid")
   677  
   678  	s.PatchValue(juju.ProviderConnectDelay, 0*time.Second)
   679  	apiOpen := func(info *api.Info, opts api.DialOpts) (juju.APIState, error) {
   680  		if info.Addrs[0] == "infoapi.invalid" {
   681  			return nil, fmt.Errorf("info connect failed")
   682  		}
   683  		return nil, fmt.Errorf("config connect failed")
   684  	}
   685  	st, err := juju.NewAPIFromStore(coretesting.SampleEnvName, store, apiOpen)
   686  	c.Check(err, gc.ErrorMatches, "config connect failed")
   687  	c.Check(st, gc.IsNil)
   688  }
   689  
   690  func defaultConfigStore(c *gc.C) configstore.Storage {
   691  	store, err := configstore.Default()
   692  	c.Assert(err, jc.ErrorIsNil)
   693  	return store
   694  }
   695  
   696  func (s *NewAPIClientSuite) TestWithBootstrapConfigAndNoEnvironmentsFile(c *gc.C) {
   697  	coretesting.MakeSampleJujuHome(c)
   698  	store := configstore.NewMem()
   699  	s.bootstrapEnv(c, coretesting.SampleEnvName, store)
   700  	info, err := store.ReadInfo(coretesting.SampleEnvName)
   701  	c.Assert(err, jc.ErrorIsNil)
   702  	c.Assert(info.BootstrapConfig(), gc.NotNil)
   703  	c.Assert(info.APIEndpoint().Addresses, gc.HasLen, 0)
   704  
   705  	err = os.Remove(osenv.JujuHomePath("environments.yaml"))
   706  	c.Assert(err, jc.ErrorIsNil)
   707  
   708  	apiOpen := func(*api.Info, api.DialOpts) (juju.APIState, error) {
   709  		return mockedAPIState(noFlags), nil
   710  	}
   711  	st, err := juju.NewAPIFromStore(coretesting.SampleEnvName, store, apiOpen)
   712  	c.Check(err, jc.ErrorIsNil)
   713  	st.Close()
   714  }
   715  
   716  func (s *NewAPIClientSuite) TestWithBootstrapConfigTakesPrecedence(c *gc.C) {
   717  	// We want to make sure that the code is using the bootstrap
   718  	// config rather than information from environments.yaml,
   719  	// even when there is an entry in environments.yaml
   720  	// We can do that by changing the info bootstrap config
   721  	// so it has a different environment name.
   722  	coretesting.WriteEnvironments(c, coretesting.MultipleEnvConfig)
   723  
   724  	store := configstore.NewMem()
   725  	s.bootstrapEnv(c, coretesting.SampleEnvName, store)
   726  	info, err := store.ReadInfo(coretesting.SampleEnvName)
   727  	c.Assert(err, jc.ErrorIsNil)
   728  
   729  	envName2 := coretesting.SampleCertName + "-2"
   730  	info2 := store.CreateInfo(envName2)
   731  	info2.SetBootstrapConfig(info.BootstrapConfig())
   732  	err = info2.Write()
   733  	c.Assert(err, jc.ErrorIsNil)
   734  
   735  	// Now we have info for envName2 which will actually
   736  	// cause a connection to the originally bootstrapped
   737  	// state.
   738  	apiOpen := func(*api.Info, api.DialOpts) (juju.APIState, error) {
   739  		return mockedAPIState(noFlags), nil
   740  	}
   741  	st, err := juju.NewAPIFromStore(envName2, store, apiOpen)
   742  	c.Check(err, jc.ErrorIsNil)
   743  	st.Close()
   744  
   745  	// Sanity check that connecting to the envName2
   746  	// but with no info fails.
   747  	// Currently this panics with an "environment not prepared" error.
   748  	// Disable for now until an upcoming branch fixes it.
   749  	//	err = info2.Destroy()
   750  	//	c.Assert(err, jc.ErrorIsNil)
   751  	//	st, err = juju.NewAPIFromStore(envName2, store)
   752  	//	if err == nil {
   753  	//		st.Close()
   754  	//	}
   755  	//	c.Assert(err, gc.ErrorMatches, "fooobie")
   756  }
   757  
   758  func assertEnvironmentName(c *gc.C, client *api.Client, expectName string) {
   759  	envInfo, err := client.EnvironmentInfo()
   760  	c.Assert(err, jc.ErrorIsNil)
   761  	c.Assert(envInfo.Name, gc.Equals, expectName)
   762  }
   763  
   764  // newConfigStoreWithError that will return the given
   765  // error from ReadInfo.
   766  func newConfigStoreWithError(err error) configstore.Storage {
   767  	return &errorConfigStorage{
   768  		Storage: configstore.NewMem(),
   769  		err:     err,
   770  	}
   771  }
   772  
   773  type errorConfigStorage struct {
   774  	configstore.Storage
   775  	err error
   776  }
   777  
   778  func (store *errorConfigStorage) ReadInfo(envName string) (configstore.EnvironInfo, error) {
   779  	return nil, store.err
   780  }
   781  
   782  type environInfo struct {
   783  	creds           configstore.APICredentials
   784  	endpoint        configstore.APIEndpoint
   785  	bootstrapConfig map[string]interface{}
   786  }
   787  
   788  // newConfigStore returns a storage that contains information
   789  // for the environment name.
   790  func newConfigStore(envName string, info *environInfo) configstore.Storage {
   791  	store := configstore.NewMem()
   792  	newInfo := store.CreateInfo(envName)
   793  	newInfo.SetAPICredentials(info.creds)
   794  	newInfo.SetAPIEndpoint(info.endpoint)
   795  	newInfo.SetBootstrapConfig(info.bootstrapConfig)
   796  	err := newInfo.Write()
   797  	if err != nil {
   798  		panic(err)
   799  	}
   800  	return store
   801  }
   802  
   803  type storageWithWriteNotify struct {
   804  	written bool
   805  	store   configstore.Storage
   806  }
   807  
   808  func (*storageWithWriteNotify) CreateInfo(envName string) configstore.EnvironInfo {
   809  	panic("CreateInfo not implemented")
   810  }
   811  
   812  func (*storageWithWriteNotify) List() ([]string, error) {
   813  	panic("List not implemented")
   814  }
   815  
   816  func (s *storageWithWriteNotify) ReadInfo(envName string) (configstore.EnvironInfo, error) {
   817  	info, err := s.store.ReadInfo(envName)
   818  	if err != nil {
   819  		return nil, err
   820  	}
   821  	return &infoWithWriteNotify{
   822  		written:     &s.written,
   823  		EnvironInfo: info,
   824  	}, nil
   825  }
   826  
   827  type infoWithWriteNotify struct {
   828  	configstore.EnvironInfo
   829  	written *bool
   830  }
   831  
   832  func (info *infoWithWriteNotify) Write() error {
   833  	*info.written = true
   834  	return info.EnvironInfo.Write()
   835  }
   836  
   837  type CacheAPIEndpointsSuite struct {
   838  	jujutesting.JujuConnSuite
   839  
   840  	hostPorts   [][]network.HostPort
   841  	envTag      names.EnvironTag
   842  	apiHostPort network.HostPort
   843  	store       configstore.Storage
   844  
   845  	resolveSeq      int
   846  	resolveNumCalls int
   847  	numResolved     int
   848  	gocheckC        *gc.C
   849  }
   850  
   851  var _ = gc.Suite(&CacheAPIEndpointsSuite{})
   852  
   853  func (s *CacheAPIEndpointsSuite) SetUpTest(c *gc.C) {
   854  	s.PatchValue(juju.ResolveOrDropHostnames, s.mockResolveOrDropHostnames)
   855  
   856  	s.hostPorts = [][]network.HostPort{
   857  		network.NewHostPorts(1234,
   858  			"1.0.0.1",
   859  			"192.0.0.1",
   860  			"127.0.0.1",
   861  			"ipv4+6.example.com",
   862  			"localhost",
   863  			"169.254.1.1",
   864  			"ipv4.example.com",
   865  			"invalid host",
   866  			"ipv6+6.example.com",
   867  			"ipv4+4.example.com",
   868  			"::1",
   869  			"fe80::1",
   870  			"ipv6.example.com",
   871  			"fc00::111",
   872  			"2001:db8::1",
   873  		),
   874  		network.NewHostPorts(1235,
   875  			"1.0.0.2",
   876  			"2001:db8::2",
   877  			"::1",
   878  			"127.0.0.1",
   879  			"ipv6+4.example.com",
   880  			"localhost",
   881  		),
   882  	}
   883  	s.gocheckC = c
   884  	s.resolveSeq = 1
   885  	s.resolveNumCalls = 0
   886  	s.numResolved = 0
   887  	s.envTag = names.NewEnvironTag(fakeUUID)
   888  	s.store = configstore.NewMem()
   889  
   890  	s.JujuConnSuite.SetUpTest(c)
   891  
   892  	apiHostPort, err := network.ParseHostPorts(s.APIState.Addr())
   893  	c.Assert(err, jc.ErrorIsNil)
   894  	s.apiHostPort = apiHostPort[0]
   895  }
   896  
   897  func (s *CacheAPIEndpointsSuite) TestPrepareEndpointsForCachingPreferIPv6True(c *gc.C) {
   898  	info := s.store.CreateInfo("env-name1")
   899  	s.PatchValue(juju.MaybePreferIPv6, func(_ configstore.EnvironInfo) bool {
   900  		return true
   901  	})
   902  	// First test cacheChangedAPIInfo behaves as expected.
   903  	err := juju.CacheChangedAPIInfo(info, s.hostPorts, s.apiHostPort, s.envTag.Id(), "")
   904  	c.Assert(err, jc.ErrorIsNil)
   905  	s.assertEndpointsPreferIPv6True(c, info)
   906  
   907  	// Now test cacheAPIInfo behaves the same way.
   908  	s.resolveSeq = 1
   909  	s.resolveNumCalls = 0
   910  	s.numResolved = 0
   911  	info = s.store.CreateInfo("env-name2")
   912  	mockAPIInfo := s.APIInfo(c)
   913  	mockAPIInfo.EnvironTag = s.envTag
   914  	hps := network.CollapseHostPorts(s.hostPorts)
   915  	mockAPIInfo.Addrs = network.HostPortsToStrings(hps)
   916  	err = juju.CacheAPIInfo(s.APIState, info, mockAPIInfo)
   917  	c.Assert(err, jc.ErrorIsNil)
   918  	s.assertEndpointsPreferIPv6True(c, info)
   919  }
   920  
   921  func (s *CacheAPIEndpointsSuite) TestPrepareEndpointsForCachingPreferIPv6False(c *gc.C) {
   922  	info := s.store.CreateInfo("env-name1")
   923  	s.PatchValue(juju.MaybePreferIPv6, func(_ configstore.EnvironInfo) bool {
   924  		return false
   925  	})
   926  	// First test cacheChangedAPIInfo behaves as expected.
   927  	err := juju.CacheChangedAPIInfo(info, s.hostPorts, s.apiHostPort, s.envTag.Id(), "")
   928  	c.Assert(err, jc.ErrorIsNil)
   929  	s.assertEndpointsPreferIPv6False(c, info)
   930  
   931  	// Now test cacheAPIInfo behaves the same way.
   932  	s.resolveSeq = 1
   933  	s.resolveNumCalls = 0
   934  	s.numResolved = 0
   935  	info = s.store.CreateInfo("env-name2")
   936  	mockAPIInfo := s.APIInfo(c)
   937  	mockAPIInfo.EnvironTag = s.envTag
   938  	hps := network.CollapseHostPorts(s.hostPorts)
   939  	mockAPIInfo.Addrs = network.HostPortsToStrings(hps)
   940  	err = juju.CacheAPIInfo(s.APIState, info, mockAPIInfo)
   941  	c.Assert(err, jc.ErrorIsNil)
   942  	s.assertEndpointsPreferIPv6False(c, info)
   943  }
   944  
   945  func (s *CacheAPIEndpointsSuite) TestResolveSkippedWhenHostnamesUnchanged(c *gc.C) {
   946  	// Test that if new endpoints hostnames are the same as the
   947  	// cached, no DNS resolution happens (i.e. we don't resolve on
   948  	// every connection, but as needed).
   949  	info := s.store.CreateInfo("env-name")
   950  	hps := network.NewHostPorts(1234,
   951  		"8.8.8.8",
   952  		"example.com",
   953  		"10.0.0.1",
   954  	)
   955  	info.SetAPIEndpoint(configstore.APIEndpoint{
   956  		Hostnames: network.HostPortsToStrings(hps),
   957  	})
   958  	err := info.Write()
   959  	c.Assert(err, jc.ErrorIsNil)
   960  
   961  	addrs, hosts, changed := juju.PrepareEndpointsForCaching(
   962  		info, [][]network.HostPort{hps}, network.HostPort{},
   963  	)
   964  	c.Assert(addrs, gc.IsNil)
   965  	c.Assert(hosts, gc.IsNil)
   966  	c.Assert(changed, jc.IsFalse)
   967  	c.Assert(s.resolveNumCalls, gc.Equals, 0)
   968  	c.Assert(
   969  		c.GetTestLog(),
   970  		jc.Contains,
   971  		"DEBUG juju.api API hostnames unchanged - not resolving",
   972  	)
   973  }
   974  
   975  func (s *CacheAPIEndpointsSuite) TestResolveCalledWithChangedHostnames(c *gc.C) {
   976  	// Test that if new endpoints hostnames are different than the
   977  	// cached hostnames DNS resolution happens and we compare resolved
   978  	// addresses.
   979  	info := s.store.CreateInfo("env-name")
   980  	// Because Hostnames are sorted before caching, reordering them
   981  	// will simulate they have changed.
   982  	unsortedHPs := network.NewHostPorts(1234,
   983  		"ipv4.example.com",
   984  		"8.8.8.8",
   985  		"ipv6.example.com",
   986  		"10.0.0.1",
   987  	)
   988  	strUnsorted := network.HostPortsToStrings(unsortedHPs)
   989  	sortedHPs := network.NewHostPorts(1234,
   990  		"8.8.8.8",
   991  		"ipv4.example.com",
   992  		"ipv6.example.com",
   993  		"10.0.0.1",
   994  	)
   995  	strSorted := network.HostPortsToStrings(sortedHPs)
   996  	resolvedHPs := network.NewHostPorts(1234,
   997  		"0.1.2.1", // from ipv4.example.com
   998  		"8.8.8.8",
   999  		"10.0.0.1",
  1000  		"fc00::2", // from ipv6.example.com
  1001  	)
  1002  	strResolved := network.HostPortsToStrings(resolvedHPs)
  1003  	info.SetAPIEndpoint(configstore.APIEndpoint{
  1004  		Hostnames: strUnsorted,
  1005  	})
  1006  	err := info.Write()
  1007  	c.Assert(err, jc.ErrorIsNil)
  1008  
  1009  	addrs, hosts, changed := juju.PrepareEndpointsForCaching(
  1010  		info, [][]network.HostPort{unsortedHPs}, network.HostPort{},
  1011  	)
  1012  	c.Assert(addrs, jc.DeepEquals, strResolved)
  1013  	c.Assert(hosts, jc.DeepEquals, strSorted)
  1014  	c.Assert(changed, jc.IsTrue)
  1015  	c.Assert(s.resolveNumCalls, gc.Equals, 1)
  1016  	c.Assert(s.numResolved, gc.Equals, 2)
  1017  	expectLog := fmt.Sprintf("DEBUG juju.api API hostnames changed from %v to %v - resolving hostnames", unsortedHPs, sortedHPs)
  1018  	c.Assert(c.GetTestLog(), jc.Contains, expectLog)
  1019  	expectLog = fmt.Sprintf("INFO juju.api new API addresses to cache %v", resolvedHPs)
  1020  	c.Assert(c.GetTestLog(), jc.Contains, expectLog)
  1021  }
  1022  
  1023  func (s *CacheAPIEndpointsSuite) TestAfterResolvingUnchangedAddressesNotCached(c *gc.C) {
  1024  	// Test that if new endpoints hostnames are different than the
  1025  	// cached hostnames, but after resolving the addresses match the
  1026  	// cached addresses, the cache is not changed.
  1027  	info := s.store.CreateInfo("env-name")
  1028  	// Because Hostnames are sorted before caching, reordering them
  1029  	// will simulate they have changed.
  1030  	unsortedHPs := network.NewHostPorts(1234,
  1031  		"ipv4.example.com",
  1032  		"8.8.8.8",
  1033  		"ipv6.example.com",
  1034  		"10.0.0.1",
  1035  	)
  1036  	strUnsorted := network.HostPortsToStrings(unsortedHPs)
  1037  	sortedHPs := network.NewHostPorts(1234,
  1038  		"8.8.8.8",
  1039  		"ipv4.example.com",
  1040  		"ipv6.example.com",
  1041  		"10.0.0.1",
  1042  	)
  1043  	resolvedHPs := network.NewHostPorts(1234,
  1044  		"0.1.2.1", // from ipv4.example.com
  1045  		"8.8.8.8",
  1046  		"10.0.0.1",
  1047  		"fc00::2", // from ipv6.example.com
  1048  	)
  1049  	strResolved := network.HostPortsToStrings(resolvedHPs)
  1050  	info.SetAPIEndpoint(configstore.APIEndpoint{
  1051  		Hostnames: strUnsorted,
  1052  		Addresses: strResolved,
  1053  	})
  1054  	err := info.Write()
  1055  	c.Assert(err, jc.ErrorIsNil)
  1056  
  1057  	addrs, hosts, changed := juju.PrepareEndpointsForCaching(
  1058  		info, [][]network.HostPort{unsortedHPs}, network.HostPort{},
  1059  	)
  1060  	c.Assert(addrs, gc.IsNil)
  1061  	c.Assert(hosts, gc.IsNil)
  1062  	c.Assert(changed, jc.IsFalse)
  1063  	c.Assert(s.resolveNumCalls, gc.Equals, 1)
  1064  	c.Assert(s.numResolved, gc.Equals, 2)
  1065  	expectLog := fmt.Sprintf("DEBUG juju.api API hostnames changed from %v to %v - resolving hostnames", unsortedHPs, sortedHPs)
  1066  	c.Assert(c.GetTestLog(), jc.Contains, expectLog)
  1067  	expectLog = "DEBUG juju.api API addresses unchanged"
  1068  	c.Assert(c.GetTestLog(), jc.Contains, expectLog)
  1069  }
  1070  
  1071  func (s *CacheAPIEndpointsSuite) TestResolveCalledWithInitialEndpoints(c *gc.C) {
  1072  	// Test that if no hostnames exist cached we call resolve (i.e.
  1073  	// simulate the behavior right after bootstrap)
  1074  	info := s.store.CreateInfo("env-name")
  1075  	// Because Hostnames are sorted before caching, reordering them
  1076  	// will simulate they have changed.
  1077  	unsortedHPs := network.NewHostPorts(1234,
  1078  		"ipv4.example.com",
  1079  		"8.8.8.8",
  1080  		"ipv6.example.com",
  1081  		"10.0.0.1",
  1082  	)
  1083  	sortedHPs := network.NewHostPorts(1234,
  1084  		"8.8.8.8",
  1085  		"ipv4.example.com",
  1086  		"ipv6.example.com",
  1087  		"10.0.0.1",
  1088  	)
  1089  	strSorted := network.HostPortsToStrings(sortedHPs)
  1090  	resolvedHPs := network.NewHostPorts(1234,
  1091  		"0.1.2.1", // from ipv4.example.com
  1092  		"8.8.8.8",
  1093  		"10.0.0.1",
  1094  		"fc00::2", // from ipv6.example.com
  1095  	)
  1096  	strResolved := network.HostPortsToStrings(resolvedHPs)
  1097  	info.SetAPIEndpoint(configstore.APIEndpoint{})
  1098  	err := info.Write()
  1099  	c.Assert(err, jc.ErrorIsNil)
  1100  
  1101  	addrs, hosts, changed := juju.PrepareEndpointsForCaching(
  1102  		info, [][]network.HostPort{unsortedHPs}, network.HostPort{},
  1103  	)
  1104  	c.Assert(addrs, jc.DeepEquals, strResolved)
  1105  	c.Assert(hosts, jc.DeepEquals, strSorted)
  1106  	c.Assert(changed, jc.IsTrue)
  1107  	c.Assert(s.resolveNumCalls, gc.Equals, 1)
  1108  	c.Assert(s.numResolved, gc.Equals, 2)
  1109  	expectLog := fmt.Sprintf("DEBUG juju.api API hostnames %v - resolving hostnames", sortedHPs)
  1110  	c.Assert(c.GetTestLog(), jc.Contains, expectLog)
  1111  	expectLog = fmt.Sprintf("INFO juju.api new API addresses to cache %v", resolvedHPs)
  1112  	c.Assert(c.GetTestLog(), jc.Contains, expectLog)
  1113  }
  1114  
  1115  func (s *CacheAPIEndpointsSuite) assertEndpointsPreferIPv6False(c *gc.C, info configstore.EnvironInfo) {
  1116  	c.Assert(s.resolveNumCalls, gc.Equals, 1)
  1117  	c.Assert(s.numResolved, gc.Equals, 10)
  1118  	endpoint := info.APIEndpoint()
  1119  	// Check Addresses after resolving.
  1120  	c.Check(endpoint.Addresses, jc.DeepEquals, []string{
  1121  		s.apiHostPort.NetAddr(), // Last endpoint successfully connected to is always on top.
  1122  		"0.1.2.1:1234",          // From ipv4+4.example.com
  1123  		"0.1.2.2:1234",          // From ipv4+4.example.com
  1124  		"0.1.2.3:1234",          // From ipv4+6.example.com
  1125  		"0.1.2.5:1234",          // From ipv4.example.com
  1126  		"0.1.2.6:1234",          // From ipv6+4.example.com
  1127  		"1.0.0.1:1234",
  1128  		"1.0.0.2:1235",
  1129  		"192.0.0.1:1234",
  1130  		"[2001:db8::1]:1234",
  1131  		"[2001:db8::2]:1235",
  1132  		"localhost:1234",  // Left intact on purpose.
  1133  		"localhost:1235",  // Left intact on purpose.
  1134  		"[fc00::10]:1234", // From ipv6.example.com
  1135  		"[fc00::111]:1234",
  1136  		"[fc00::3]:1234", // From ipv4+6.example.com
  1137  		"[fc00::6]:1234", // From ipv6+4.example.com
  1138  		"[fc00::8]:1234", // From ipv6+6.example.com
  1139  		"[fc00::9]:1234", // From ipv6+6.example.com
  1140  	})
  1141  	// Check Hostnames before resolving
  1142  	c.Check(endpoint.Hostnames, jc.DeepEquals, []string{
  1143  		s.apiHostPort.NetAddr(), // Last endpoint successfully connected to is always on top.
  1144  		"1.0.0.1:1234",
  1145  		"1.0.0.2:1235",
  1146  		"192.0.0.1:1234",
  1147  		"[2001:db8::1]:1234",
  1148  		"[2001:db8::2]:1235",
  1149  		"invalid host:1234",
  1150  		"ipv4+4.example.com:1234",
  1151  		"ipv4+6.example.com:1234",
  1152  		"ipv4.example.com:1234",
  1153  		"ipv6+4.example.com:1235",
  1154  		"ipv6+6.example.com:1234",
  1155  		"ipv6.example.com:1234",
  1156  		"localhost:1234",
  1157  		"localhost:1235",
  1158  		"[fc00::111]:1234",
  1159  	})
  1160  }
  1161  
  1162  func (s *CacheAPIEndpointsSuite) assertEndpointsPreferIPv6True(c *gc.C, info configstore.EnvironInfo) {
  1163  	c.Assert(s.resolveNumCalls, gc.Equals, 1)
  1164  	c.Assert(s.numResolved, gc.Equals, 10)
  1165  	endpoint := info.APIEndpoint()
  1166  	// Check Addresses after resolving.
  1167  	c.Check(endpoint.Addresses, jc.DeepEquals, []string{
  1168  		s.apiHostPort.NetAddr(), // Last endpoint successfully connected to is always on top.
  1169  		"[2001:db8::1]:1234",
  1170  		"[2001:db8::2]:1235",
  1171  		"0.1.2.1:1234", // From ipv4+4.example.com
  1172  		"0.1.2.2:1234", // From ipv4+4.example.com
  1173  		"0.1.2.3:1234", // From ipv4+6.example.com
  1174  		"0.1.2.5:1234", // From ipv4.example.com
  1175  		"0.1.2.6:1234", // From ipv6+4.example.com
  1176  		"1.0.0.1:1234",
  1177  		"1.0.0.2:1235",
  1178  		"192.0.0.1:1234",
  1179  		"localhost:1234",  // Left intact on purpose.
  1180  		"localhost:1235",  // Left intact on purpose.
  1181  		"[fc00::10]:1234", // From ipv6.example.com
  1182  		"[fc00::111]:1234",
  1183  		"[fc00::3]:1234", // From ipv4+6.example.com
  1184  		"[fc00::6]:1234", // From ipv6+4.example.com
  1185  		"[fc00::8]:1234", // From ipv6+6.example.com
  1186  		"[fc00::9]:1234", // From ipv6+6.example.com
  1187  	})
  1188  	// Check Hostnames before resolving
  1189  	c.Check(endpoint.Hostnames, jc.DeepEquals, []string{
  1190  		s.apiHostPort.NetAddr(), // Last endpoint successfully connected to is always on top.
  1191  		"[2001:db8::1]:1234",
  1192  		"[2001:db8::2]:1235",
  1193  		"1.0.0.1:1234",
  1194  		"1.0.0.2:1235",
  1195  		"192.0.0.1:1234",
  1196  		"invalid host:1234",
  1197  		"ipv4+4.example.com:1234",
  1198  		"ipv4+6.example.com:1234",
  1199  		"ipv4.example.com:1234",
  1200  		"ipv6+4.example.com:1235",
  1201  		"ipv6+6.example.com:1234",
  1202  		"ipv6.example.com:1234",
  1203  		"localhost:1234",
  1204  		"localhost:1235",
  1205  		"[fc00::111]:1234",
  1206  	})
  1207  }
  1208  
  1209  func (s *CacheAPIEndpointsSuite) nextHostPorts(host string, types ...network.AddressType) []network.HostPort {
  1210  	result := make([]network.HostPort, len(types))
  1211  	num4, num6 := 0, 0
  1212  	for i, tp := range types {
  1213  		addr := ""
  1214  		switch tp {
  1215  		case network.IPv4Address:
  1216  			addr = fmt.Sprintf("0.1.2.%d", s.resolveSeq+num4)
  1217  			num4++
  1218  		case network.IPv6Address:
  1219  			addr = fmt.Sprintf("fc00::%d", s.resolveSeq+num6)
  1220  			num6++
  1221  		}
  1222  		result[i] = network.NewHostPorts(1234, addr)[0]
  1223  	}
  1224  	s.resolveSeq += num4 + num6
  1225  	s.gocheckC.Logf("resolving %q as %v", host, result)
  1226  	return result
  1227  }
  1228  
  1229  func (s *CacheAPIEndpointsSuite) mockResolveOrDropHostnames(hps []network.HostPort) []network.HostPort {
  1230  	s.resolveNumCalls++
  1231  	var result []network.HostPort
  1232  	for _, hp := range hps {
  1233  		if hp.Value == "invalid host" || hp.Scope == network.ScopeLinkLocal {
  1234  			// Simulate we dropped this.
  1235  			continue
  1236  		} else if hp.Value == "localhost" || hp.Type != network.HostName {
  1237  			// Leave localhost and IPs alone.
  1238  			result = append(result, hp)
  1239  			continue
  1240  		}
  1241  		var types []network.AddressType
  1242  		switch strings.TrimSuffix(hp.Value, ".example.com") {
  1243  		case "ipv4":
  1244  			// Simulate it resolves to an IPv4 address.
  1245  			types = append(types, network.IPv4Address)
  1246  		case "ipv6":
  1247  			// Simulate it resolves to an IPv6 address.
  1248  			types = append(types, network.IPv6Address)
  1249  		case "ipv4+6":
  1250  			// Simulate it resolves to both IPv4 and IPv6 addresses.
  1251  			types = append(types, network.IPv4Address, network.IPv6Address)
  1252  		case "ipv6+6":
  1253  			// Simulate it resolves to two IPv6 addresses.
  1254  			types = append(types, network.IPv6Address, network.IPv6Address)
  1255  		case "ipv4+4":
  1256  			// Simulate it resolves to two IPv4 addresses.
  1257  			types = append(types, network.IPv4Address, network.IPv4Address)
  1258  		case "ipv6+4":
  1259  			// Simulate it resolves to both IPv4 and IPv6 addresses.
  1260  			types = append(types, network.IPv6Address, network.IPv4Address)
  1261  		}
  1262  		result = append(result, s.nextHostPorts(hp.Value, types...)...)
  1263  		s.numResolved += len(types)
  1264  	}
  1265  	return result
  1266  }
  1267  
  1268  var fakeUUID = "df136476-12e9-11e4-8a70-b2227cce2b54"
  1269  
  1270  var dummyStoreInfo = &environInfo{
  1271  	creds: configstore.APICredentials{
  1272  		User:     "foo",
  1273  		Password: "foopass",
  1274  	},
  1275  	endpoint: configstore.APIEndpoint{
  1276  		Addresses:   []string{"foo.invalid"},
  1277  		CACert:      "certificated",
  1278  		EnvironUUID: fakeUUID,
  1279  	},
  1280  }
  1281  
  1282  type EnvironInfoTest struct {
  1283  	coretesting.BaseSuite
  1284  }
  1285  
  1286  var _ = gc.Suite(&EnvironInfoTest{})
  1287  
  1288  func (*EnvironInfoTest) TestNullInfo(c *gc.C) {
  1289  	c.Assert(juju.EnvironInfoUserTag(nil), gc.Equals, names.NewUserTag(configstore.DefaultAdminUsername))
  1290  }
  1291  
  1292  type fakeEnvironInfo struct {
  1293  	configstore.EnvironInfo
  1294  	user string
  1295  }
  1296  
  1297  func (fake *fakeEnvironInfo) APICredentials() configstore.APICredentials {
  1298  	return configstore.APICredentials{User: fake.user}
  1299  }
  1300  
  1301  func (*EnvironInfoTest) TestEmptyUser(c *gc.C) {
  1302  	info := &fakeEnvironInfo{}
  1303  	c.Assert(juju.EnvironInfoUserTag(info), gc.Equals, names.NewUserTag(configstore.DefaultAdminUsername))
  1304  }
  1305  
  1306  func (*EnvironInfoTest) TestRealUser(c *gc.C) {
  1307  	info := &fakeEnvironInfo{user: "eric"}
  1308  	c.Assert(juju.EnvironInfoUserTag(info), gc.Equals, names.NewUserTag("eric"))
  1309  }