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