github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/juju/apiconn_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  	"os"
     9  	"time"
    10  
    11  	"github.com/juju/errors"
    12  	jc "github.com/juju/testing/checkers"
    13  	gc "launchpad.net/gocheck"
    14  
    15  	"github.com/juju/juju/environs"
    16  	"github.com/juju/juju/environs/bootstrap"
    17  	"github.com/juju/juju/environs/config"
    18  	"github.com/juju/juju/environs/configstore"
    19  	envtesting "github.com/juju/juju/environs/testing"
    20  	"github.com/juju/juju/instance"
    21  	"github.com/juju/juju/juju"
    22  	"github.com/juju/juju/juju/osenv"
    23  	"github.com/juju/juju/provider/dummy"
    24  	"github.com/juju/juju/state/api"
    25  	coretesting "github.com/juju/juju/testing"
    26  )
    27  
    28  type NewAPIConnSuite struct {
    29  	coretesting.FakeJujuHomeSuite
    30  	envtesting.ToolsFixture
    31  }
    32  
    33  var _ = gc.Suite(&NewAPIConnSuite{})
    34  
    35  func (cs *NewAPIConnSuite) SetUpTest(c *gc.C) {
    36  	cs.FakeJujuHomeSuite.SetUpTest(c)
    37  	cs.ToolsFixture.SetUpTest(c)
    38  }
    39  
    40  func (cs *NewAPIConnSuite) TearDownTest(c *gc.C) {
    41  	dummy.Reset()
    42  	cs.ToolsFixture.TearDownTest(c)
    43  	cs.FakeJujuHomeSuite.TearDownTest(c)
    44  }
    45  
    46  func (*NewAPIConnSuite) TestNewConn(c *gc.C) {
    47  	cfg, err := config.New(config.NoDefaults, dummy.SampleConfig())
    48  	c.Assert(err, gc.IsNil)
    49  	ctx := coretesting.Context(c)
    50  	env, err := environs.Prepare(cfg, ctx, configstore.NewMem())
    51  	c.Assert(err, gc.IsNil)
    52  
    53  	envtesting.UploadFakeTools(c, env.Storage())
    54  	err = bootstrap.Bootstrap(ctx, env, environs.BootstrapParams{})
    55  	c.Assert(err, gc.IsNil)
    56  
    57  	cfg = env.Config()
    58  	cfg, err = cfg.Apply(map[string]interface{}{
    59  		"secret": "fnord",
    60  	})
    61  	c.Assert(err, gc.IsNil)
    62  	err = env.SetConfig(cfg)
    63  	c.Assert(err, gc.IsNil)
    64  
    65  	conn, err := juju.NewAPIConn(env, api.DefaultDialOpts())
    66  	c.Assert(err, gc.IsNil)
    67  	c.Assert(conn.Environ, gc.Equals, env)
    68  	c.Assert(conn.State, gc.NotNil)
    69  
    70  	// the secrets will not be updated, as they already exist
    71  	attrs, err := conn.State.Client().EnvironmentGet()
    72  	c.Assert(attrs["secret"], gc.Equals, "pork")
    73  
    74  	c.Assert(conn.Close(), gc.IsNil)
    75  }
    76  
    77  type NewAPIClientSuite struct {
    78  	coretesting.FakeJujuHomeSuite
    79  }
    80  
    81  var _ = gc.Suite(&NewAPIClientSuite{})
    82  
    83  func (cs *NewAPIClientSuite) TearDownTest(c *gc.C) {
    84  	dummy.Reset()
    85  	cs.FakeJujuHomeSuite.TearDownTest(c)
    86  }
    87  
    88  func (s *NewAPIClientSuite) TestNameDefault(c *gc.C) {
    89  	coretesting.WriteEnvironments(c, coretesting.MultipleEnvConfig)
    90  	// The connection logic should not delay the config connection
    91  	// at all when there is no environment info available.
    92  	// Make sure of that by providing a suitably long delay
    93  	// and checking that the connection happens within that
    94  	// time.
    95  	s.PatchValue(juju.ProviderConnectDelay, coretesting.LongWait)
    96  	bootstrapEnv(c, coretesting.SampleEnvName, defaultConfigStore(c))
    97  
    98  	startTime := time.Now()
    99  	apiclient, err := juju.NewAPIClientFromName("")
   100  	c.Assert(err, gc.IsNil)
   101  	defer apiclient.Close()
   102  	c.Assert(time.Since(startTime), jc.LessThan, coretesting.LongWait)
   103  
   104  	// We should get the default sample environment if we ask for ""
   105  	assertEnvironmentName(c, apiclient, coretesting.SampleEnvName)
   106  }
   107  
   108  func (*NewAPIClientSuite) TestNameNotDefault(c *gc.C) {
   109  	coretesting.WriteEnvironments(c, coretesting.MultipleEnvConfig)
   110  	envName := coretesting.SampleCertName + "-2"
   111  	bootstrapEnv(c, envName, defaultConfigStore(c))
   112  	apiclient, err := juju.NewAPIClientFromName(envName)
   113  	c.Assert(err, gc.IsNil)
   114  	defer apiclient.Close()
   115  	assertEnvironmentName(c, apiclient, envName)
   116  }
   117  
   118  func (s *NewAPIClientSuite) TestWithInfoOnly(c *gc.C) {
   119  	store := newConfigStore("noconfig", dummyStoreInfo)
   120  
   121  	called := 0
   122  	expectState := &mockAPIState{
   123  		apiHostPorts: [][]instance.HostPort{
   124  			instance.AddressesWithPort([]instance.Address{instance.NewAddress("0.1.2.3", instance.NetworkUnknown)}, 1234),
   125  		},
   126  		environTag: "environment-fake-uuid",
   127  	}
   128  	apiOpen := func(apiInfo *api.Info, opts api.DialOpts) (juju.APIState, error) {
   129  		checkCommonAPIInfoAttrs(c, apiInfo, opts)
   130  		c.Check(apiInfo.EnvironTag, gc.Equals, "environment-fake-uuid")
   131  		called++
   132  		return expectState, nil
   133  	}
   134  
   135  	// Give NewAPIFromStore a store interface that can report when the
   136  	// config was written to, to check if the cache is updated.
   137  	mockStore := &storageWithWriteNotify{store: store}
   138  	st, err := juju.NewAPIFromStore("noconfig", mockStore, apiOpen)
   139  	c.Assert(err, gc.IsNil)
   140  	c.Assert(st, gc.Equals, expectState)
   141  	c.Assert(called, gc.Equals, 1)
   142  	c.Assert(mockStore.written, jc.IsTrue)
   143  	info, err := store.ReadInfo("noconfig")
   144  	c.Assert(err, gc.IsNil)
   145  	ep := info.APIEndpoint()
   146  	c.Assert(ep.Addresses, gc.DeepEquals, []string{"0.1.2.3:1234"})
   147  	c.Check(ep.EnvironUUID, gc.Equals, "fake-uuid")
   148  	mockStore.written = false
   149  
   150  	// If APIHostPorts haven't changed, then the store won't be updated.
   151  	st, err = juju.NewAPIFromStore("noconfig", mockStore, apiOpen)
   152  	c.Assert(err, gc.IsNil)
   153  	c.Assert(st, gc.Equals, expectState)
   154  	c.Assert(called, gc.Equals, 2)
   155  	c.Assert(mockStore.written, jc.IsFalse)
   156  }
   157  
   158  func (s *NewAPIClientSuite) TestWithConfigAndNoInfo(c *gc.C) {
   159  	coretesting.MakeSampleJujuHome(c)
   160  
   161  	store := newConfigStore(coretesting.SampleEnvName, &environInfo{
   162  		bootstrapConfig: map[string]interface{}{
   163  			"type":                      "dummy",
   164  			"name":                      "myenv",
   165  			"state-server":              true,
   166  			"authorized-keys":           "i-am-a-key",
   167  			"default-series":            config.LatestLtsSeries(),
   168  			"firewall-mode":             config.FwInstance,
   169  			"development":               false,
   170  			"ssl-hostname-verification": true,
   171  			"admin-secret":              "adminpass",
   172  		},
   173  	})
   174  	bootstrapEnv(c, coretesting.SampleEnvName, store)
   175  
   176  	// Verify the cache is empty.
   177  	info, err := store.ReadInfo("myenv")
   178  	c.Assert(err, gc.IsNil)
   179  	c.Assert(info, gc.NotNil)
   180  	c.Assert(info.APIEndpoint(), jc.DeepEquals, configstore.APIEndpoint{})
   181  	c.Assert(info.APICredentials(), jc.DeepEquals, configstore.APICredentials{})
   182  
   183  	called := 0
   184  	expectState := &mockAPIState{}
   185  	apiOpen := func(apiInfo *api.Info, opts api.DialOpts) (juju.APIState, error) {
   186  		c.Check(apiInfo.Tag, gc.Equals, "user-admin")
   187  		c.Check(string(apiInfo.CACert), gc.Not(gc.Equals), "")
   188  		c.Check(apiInfo.Password, gc.Equals, "adminpass")
   189  		// EnvironTag wasn't in regular Config
   190  		c.Check(apiInfo.EnvironTag, gc.Equals, "")
   191  		c.Check(opts, gc.DeepEquals, api.DefaultDialOpts())
   192  		called++
   193  		return expectState, nil
   194  	}
   195  	st, err := juju.NewAPIFromStore("myenv", store, apiOpen)
   196  	c.Assert(err, gc.IsNil)
   197  	c.Assert(st, gc.Equals, expectState)
   198  	c.Assert(called, gc.Equals, 1)
   199  
   200  	// Make sure the cache is updated.
   201  	info, err = store.ReadInfo("myenv")
   202  	c.Assert(err, gc.IsNil)
   203  	c.Assert(info, gc.NotNil)
   204  	ep := info.APIEndpoint()
   205  	c.Assert(ep.Addresses, gc.HasLen, 1)
   206  	c.Check(ep.Addresses[0], gc.Matches, `127\.0\.0\.1:\d+`)
   207  	c.Check(ep.CACert, gc.Not(gc.Equals), "")
   208  	// Old servers won't hand back EnvironTag, so it should stay empty in
   209  	// the cache
   210  	c.Check(ep.EnvironUUID, gc.Equals, "")
   211  	creds := info.APICredentials()
   212  	c.Check(creds.User, gc.Equals, "admin")
   213  	c.Check(creds.Password, gc.Equals, "adminpass")
   214  }
   215  
   216  func (s *NewAPIClientSuite) TestWithInfoError(c *gc.C) {
   217  	expectErr := fmt.Errorf("an error")
   218  	store := newConfigStoreWithError(expectErr)
   219  	client, err := juju.NewAPIFromStore("noconfig", store, panicAPIOpen)
   220  	c.Assert(err, gc.Equals, expectErr)
   221  	c.Assert(client, gc.IsNil)
   222  }
   223  
   224  func (s *NewAPIClientSuite) TestWithInfoNoAddresses(c *gc.C) {
   225  	store := newConfigStore("noconfig", &environInfo{
   226  		endpoint: configstore.APIEndpoint{
   227  			Addresses: []string{},
   228  			CACert:    "certificated",
   229  		},
   230  	})
   231  	st, err := juju.NewAPIFromStore("noconfig", store, panicAPIOpen)
   232  	c.Assert(err, gc.ErrorMatches, `environment "noconfig" not found`)
   233  	c.Assert(st, gc.IsNil)
   234  }
   235  
   236  var noTagStoreInfo = &environInfo{
   237  	creds: configstore.APICredentials{
   238  		User:     "foo",
   239  		Password: "foopass",
   240  	},
   241  	endpoint: configstore.APIEndpoint{
   242  		Addresses: []string{"foo.invalid"},
   243  		CACert:    "certificated",
   244  	},
   245  }
   246  
   247  func mockedAPIState(hasHostPort, hasEnvironTag bool) *mockAPIState {
   248  	apiHostPorts := [][]instance.HostPort{}
   249  	if hasHostPort {
   250  		address := instance.NewAddress("0.1.2.3", instance.NetworkUnknown)
   251  		apiHostPorts = [][]instance.HostPort{
   252  			instance.AddressesWithPort([]instance.Address{address}, 1234),
   253  		}
   254  	}
   255  	environTag := ""
   256  	if hasEnvironTag {
   257  		environTag = "environment-fake-uuid"
   258  	}
   259  	return &mockAPIState{
   260  		apiHostPorts: apiHostPorts,
   261  		environTag:   environTag,
   262  	}
   263  }
   264  
   265  func checkCommonAPIInfoAttrs(c *gc.C, apiInfo *api.Info, opts api.DialOpts) {
   266  	c.Check(apiInfo.Tag, gc.Equals, "user-foo")
   267  	c.Check(string(apiInfo.CACert), gc.Equals, "certificated")
   268  	c.Check(apiInfo.Password, gc.Equals, "foopass")
   269  	c.Check(opts, gc.DeepEquals, api.DefaultDialOpts())
   270  }
   271  
   272  func (s *NewAPIClientSuite) TestWithInfoNoEnvironTag(c *gc.C) {
   273  	store := newConfigStore("noconfig", noTagStoreInfo)
   274  
   275  	called := 0
   276  	expectState := mockedAPIState(true, true)
   277  	apiOpen := func(apiInfo *api.Info, opts api.DialOpts) (juju.APIState, error) {
   278  		checkCommonAPIInfoAttrs(c, apiInfo, opts)
   279  		c.Check(apiInfo.EnvironTag, gc.Equals, "")
   280  		called++
   281  		return expectState, nil
   282  	}
   283  
   284  	// Give NewAPIFromStore a store interface that can report when the
   285  	// config was written to, to check if the cache is updated.
   286  	mockStore := &storageWithWriteNotify{store: store}
   287  	st, err := juju.NewAPIFromStore("noconfig", mockStore, apiOpen)
   288  	c.Assert(err, gc.IsNil)
   289  	c.Assert(st, gc.Equals, expectState)
   290  	c.Assert(called, gc.Equals, 1)
   291  	c.Assert(mockStore.written, jc.IsTrue)
   292  	info, err := store.ReadInfo("noconfig")
   293  	c.Assert(err, gc.IsNil)
   294  	c.Assert(info.APIEndpoint().Addresses, gc.DeepEquals, []string{"0.1.2.3:1234"})
   295  	c.Check(info.APIEndpoint().EnvironUUID, gc.Equals, "fake-uuid")
   296  }
   297  
   298  func (s *NewAPIClientSuite) TestWithInfoNoAPIHostports(c *gc.C) {
   299  	// The local cache doesn't have an EnvironTag, which the API does
   300  	// return. However, the API doesn't have apiHostPorts, we don't want to
   301  	// override the local cache with bad endpoints.
   302  	store := newConfigStore("noconfig", noTagStoreInfo)
   303  
   304  	called := 0
   305  	expectState := mockedAPIState(false, true)
   306  	apiOpen := func(apiInfo *api.Info, opts api.DialOpts) (juju.APIState, error) {
   307  		checkCommonAPIInfoAttrs(c, apiInfo, opts)
   308  		c.Check(apiInfo.EnvironTag, gc.Equals, "")
   309  		called++
   310  		return expectState, nil
   311  	}
   312  
   313  	mockStore := &storageWithWriteNotify{store: store}
   314  	st, err := juju.NewAPIFromStore("noconfig", mockStore, apiOpen)
   315  	c.Assert(err, gc.IsNil)
   316  	c.Assert(st, gc.Equals, expectState)
   317  	c.Assert(called, gc.Equals, 1)
   318  	c.Assert(mockStore.written, jc.IsTrue)
   319  	info, err := store.ReadInfo("noconfig")
   320  	c.Assert(err, gc.IsNil)
   321  	ep := info.APIEndpoint()
   322  	// We should have cached the environ tag, but not disturbed the
   323  	// Addresses
   324  	c.Check(ep.Addresses, gc.HasLen, 1)
   325  	c.Check(ep.Addresses[0], gc.Matches, `foo\.invalid`)
   326  	c.Check(ep.EnvironUUID, gc.Equals, "fake-uuid")
   327  }
   328  
   329  func (s *NewAPIClientSuite) TestNoEnvironTagDoesntOverwriteCached(c *gc.C) {
   330  	store := newConfigStore("noconfig", dummyStoreInfo)
   331  	called := 0
   332  	// State returns a new set of APIHostPorts but not a new EnvironTag. We
   333  	// shouldn't override the cached value with environ tag of "".
   334  	expectState := mockedAPIState(true, false)
   335  	apiOpen := func(apiInfo *api.Info, opts api.DialOpts) (juju.APIState, error) {
   336  		checkCommonAPIInfoAttrs(c, apiInfo, opts)
   337  		c.Check(apiInfo.EnvironTag, gc.Equals, "environment-fake-uuid")
   338  		called++
   339  		return expectState, nil
   340  	}
   341  
   342  	mockStore := &storageWithWriteNotify{store: store}
   343  	st, err := juju.NewAPIFromStore("noconfig", mockStore, apiOpen)
   344  	c.Assert(err, gc.IsNil)
   345  	c.Assert(st, gc.Equals, expectState)
   346  	c.Assert(called, gc.Equals, 1)
   347  	c.Assert(mockStore.written, jc.IsTrue)
   348  	info, err := store.ReadInfo("noconfig")
   349  	c.Assert(err, gc.IsNil)
   350  	ep := info.APIEndpoint()
   351  	c.Assert(ep.Addresses, gc.DeepEquals, []string{"0.1.2.3:1234"})
   352  	c.Check(ep.EnvironUUID, gc.Equals, "fake-uuid")
   353  }
   354  
   355  func (s *NewAPIClientSuite) TestWithInfoAPIOpenError(c *gc.C) {
   356  	store := newConfigStore("noconfig", &environInfo{
   357  		endpoint: configstore.APIEndpoint{
   358  			Addresses: []string{"foo.invalid"},
   359  		},
   360  	})
   361  
   362  	expectErr := fmt.Errorf("an error")
   363  	apiOpen := func(apiInfo *api.Info, opts api.DialOpts) (juju.APIState, error) {
   364  		return nil, expectErr
   365  	}
   366  	st, err := juju.NewAPIFromStore("noconfig", store, apiOpen)
   367  	c.Assert(err, gc.Equals, expectErr)
   368  	c.Assert(st, gc.IsNil)
   369  }
   370  
   371  func (s *NewAPIClientSuite) TestWithSlowInfoConnect(c *gc.C) {
   372  	coretesting.MakeSampleJujuHome(c)
   373  	store := configstore.NewMem()
   374  	bootstrapEnv(c, coretesting.SampleEnvName, store)
   375  	setEndpointAddress(c, store, coretesting.SampleEnvName, "infoapi.invalid")
   376  
   377  	infoOpenedState := &mockAPIState{}
   378  	infoEndpointOpened := make(chan struct{})
   379  	cfgOpenedState := &mockAPIState{}
   380  	// On a sample run with no delay, the logic took 45ms to run, so
   381  	// we make the delay slightly more than that, so that if the
   382  	// logic doesn't delay at all, the test will fail reasonably consistently.
   383  	s.PatchValue(juju.ProviderConnectDelay, 50*time.Millisecond)
   384  	apiOpen := func(info *api.Info, opts api.DialOpts) (juju.APIState, error) {
   385  		if info.Addrs[0] == "infoapi.invalid" {
   386  			infoEndpointOpened <- struct{}{}
   387  			return infoOpenedState, nil
   388  		}
   389  		return cfgOpenedState, nil
   390  	}
   391  
   392  	stateClosed := make(chan juju.APIState)
   393  	infoOpenedState.close = func(st juju.APIState) error {
   394  		stateClosed <- st
   395  		return nil
   396  	}
   397  	cfgOpenedState.close = infoOpenedState.close
   398  
   399  	startTime := time.Now()
   400  	st, err := juju.NewAPIFromStore(coretesting.SampleEnvName, store, apiOpen)
   401  	c.Assert(err, gc.IsNil)
   402  	// The connection logic should wait for some time before opening
   403  	// the API from the configuration.
   404  	c.Assert(time.Since(startTime), jc.GreaterThan, *juju.ProviderConnectDelay)
   405  	c.Assert(st, gc.Equals, cfgOpenedState)
   406  
   407  	select {
   408  	case <-infoEndpointOpened:
   409  	case <-time.After(coretesting.LongWait):
   410  		c.Errorf("api never opened via info")
   411  	}
   412  
   413  	// Check that the ignored state was closed.
   414  	select {
   415  	case st := <-stateClosed:
   416  		c.Assert(st, gc.Equals, infoOpenedState)
   417  	case <-time.After(coretesting.LongWait):
   418  		c.Errorf("timed out waiting for state to be closed")
   419  	}
   420  }
   421  
   422  type badBootstrapInfo struct {
   423  	configstore.EnvironInfo
   424  }
   425  
   426  // BootstrapConfig is returned as a map with real content, but the content
   427  // isn't actually valid configuration, causing config.New to fail
   428  func (m *badBootstrapInfo) BootstrapConfig() map[string]interface{} {
   429  	return map[string]interface{}{"something": "else"}
   430  }
   431  
   432  func (s *NewAPIClientSuite) TestBadConfigDoesntPanic(c *gc.C) {
   433  	badInfo := &badBootstrapInfo{}
   434  	cfg, err := juju.GetConfig(badInfo, nil, "test")
   435  	// The specific error we get depends on what key is invalid, which is a
   436  	// bit spurious, but what we care about is that we didn't get a panic,
   437  	// but instead got an error
   438  	c.Assert(err, gc.ErrorMatches, ".*expected.*got nothing")
   439  	c.Assert(cfg, gc.IsNil)
   440  }
   441  
   442  func setEndpointAddress(c *gc.C, store configstore.Storage, envName string, addr string) {
   443  	// Populate the environment's info with an endpoint
   444  	// with a known address.
   445  	info, err := store.ReadInfo(coretesting.SampleEnvName)
   446  	c.Assert(err, gc.IsNil)
   447  	info.SetAPIEndpoint(configstore.APIEndpoint{
   448  		Addresses: []string{addr},
   449  		CACert:    "certificated",
   450  	})
   451  	err = info.Write()
   452  	c.Assert(err, gc.IsNil)
   453  }
   454  
   455  func (s *NewAPIClientSuite) TestWithSlowConfigConnect(c *gc.C) {
   456  	coretesting.MakeSampleJujuHome(c)
   457  
   458  	store := configstore.NewMem()
   459  	bootstrapEnv(c, coretesting.SampleEnvName, store)
   460  	setEndpointAddress(c, store, coretesting.SampleEnvName, "infoapi.invalid")
   461  
   462  	infoOpenedState := &mockAPIState{}
   463  	infoEndpointOpened := make(chan struct{})
   464  	cfgOpenedState := &mockAPIState{}
   465  	cfgEndpointOpened := make(chan struct{})
   466  
   467  	s.PatchValue(juju.ProviderConnectDelay, 0*time.Second)
   468  	apiOpen := func(info *api.Info, opts api.DialOpts) (juju.APIState, error) {
   469  		if info.Addrs[0] == "infoapi.invalid" {
   470  			infoEndpointOpened <- struct{}{}
   471  			<-infoEndpointOpened
   472  			return infoOpenedState, nil
   473  		}
   474  		cfgEndpointOpened <- struct{}{}
   475  		<-cfgEndpointOpened
   476  		return cfgOpenedState, nil
   477  	}
   478  
   479  	stateClosed := make(chan juju.APIState)
   480  	infoOpenedState.close = func(st juju.APIState) error {
   481  		stateClosed <- st
   482  		return nil
   483  	}
   484  	cfgOpenedState.close = infoOpenedState.close
   485  
   486  	done := make(chan struct{})
   487  	go func() {
   488  		st, err := juju.NewAPIFromStore(coretesting.SampleEnvName, store, apiOpen)
   489  		c.Check(err, gc.IsNil)
   490  		c.Check(st, gc.Equals, infoOpenedState)
   491  		close(done)
   492  	}()
   493  
   494  	// Check that we're trying to connect to both endpoints:
   495  	select {
   496  	case <-infoEndpointOpened:
   497  	case <-time.After(coretesting.LongWait):
   498  		c.Fatalf("api never opened via info")
   499  	}
   500  	select {
   501  	case <-cfgEndpointOpened:
   502  	case <-time.After(coretesting.LongWait):
   503  		c.Fatalf("api never opened via config")
   504  	}
   505  	// Let the info endpoint open go ahead and
   506  	// check that the NewAPIFromStore call returns.
   507  	infoEndpointOpened <- struct{}{}
   508  	select {
   509  	case <-done:
   510  	case <-time.After(coretesting.LongWait):
   511  		c.Errorf("timed out opening API")
   512  	}
   513  
   514  	// Let the config endpoint open go ahead and
   515  	// check that its state is closed.
   516  	cfgEndpointOpened <- struct{}{}
   517  	select {
   518  	case st := <-stateClosed:
   519  		c.Assert(st, gc.Equals, cfgOpenedState)
   520  	case <-time.After(coretesting.LongWait):
   521  		c.Errorf("timed out waiting for state to be closed")
   522  	}
   523  }
   524  
   525  func (s *NewAPIClientSuite) TestBothError(c *gc.C) {
   526  	coretesting.MakeSampleJujuHome(c)
   527  	store := configstore.NewMem()
   528  	bootstrapEnv(c, coretesting.SampleEnvName, store)
   529  	setEndpointAddress(c, store, coretesting.SampleEnvName, "infoapi.invalid")
   530  
   531  	s.PatchValue(juju.ProviderConnectDelay, 0*time.Second)
   532  	apiOpen := func(info *api.Info, opts api.DialOpts) (juju.APIState, error) {
   533  		if info.Addrs[0] == "infoapi.invalid" {
   534  			return nil, fmt.Errorf("info connect failed")
   535  		}
   536  		return nil, fmt.Errorf("config connect failed")
   537  	}
   538  	st, err := juju.NewAPIFromStore(coretesting.SampleEnvName, store, apiOpen)
   539  	c.Check(err, gc.ErrorMatches, "config connect failed")
   540  	c.Check(st, gc.IsNil)
   541  }
   542  
   543  func defaultConfigStore(c *gc.C) configstore.Storage {
   544  	store, err := configstore.Default()
   545  	c.Assert(err, gc.IsNil)
   546  	return store
   547  }
   548  
   549  // TODO(jam): 2013-08-27 This should move somewhere in api.*
   550  func (s *NewAPIClientSuite) TestMultipleCloseOk(c *gc.C) {
   551  	coretesting.MakeSampleJujuHome(c)
   552  	bootstrapEnv(c, "", defaultConfigStore(c))
   553  	client, _ := juju.NewAPIClientFromName("")
   554  	c.Assert(client.Close(), gc.IsNil)
   555  	c.Assert(client.Close(), gc.IsNil)
   556  	c.Assert(client.Close(), gc.IsNil)
   557  }
   558  
   559  func (s *NewAPIClientSuite) TestWithBootstrapConfigAndNoEnvironmentsFile(c *gc.C) {
   560  	coretesting.MakeSampleJujuHome(c)
   561  	store := configstore.NewMem()
   562  	bootstrapEnv(c, coretesting.SampleEnvName, store)
   563  	info, err := store.ReadInfo(coretesting.SampleEnvName)
   564  	c.Assert(err, gc.IsNil)
   565  	c.Assert(info.BootstrapConfig(), gc.NotNil)
   566  	c.Assert(info.APIEndpoint().Addresses, gc.HasLen, 0)
   567  
   568  	err = os.Remove(osenv.JujuHomePath("environments.yaml"))
   569  	c.Assert(err, gc.IsNil)
   570  
   571  	apiOpen := func(*api.Info, api.DialOpts) (juju.APIState, error) {
   572  		return &mockAPIState{}, nil
   573  	}
   574  	st, err := juju.NewAPIFromStore(coretesting.SampleEnvName, store, apiOpen)
   575  	c.Check(err, gc.IsNil)
   576  	st.Close()
   577  }
   578  
   579  func (*NewAPIClientSuite) TestWithBootstrapConfigTakesPrecedence(c *gc.C) {
   580  	// We want to make sure that the code is using the bootstrap
   581  	// config rather than information from environments.yaml,
   582  	// even when there is an entry in environments.yaml
   583  	// We can do that by changing the info bootstrap config
   584  	// so it has a different environment name.
   585  	coretesting.WriteEnvironments(c, coretesting.MultipleEnvConfig)
   586  
   587  	store := configstore.NewMem()
   588  	bootstrapEnv(c, coretesting.SampleEnvName, store)
   589  	info, err := store.ReadInfo(coretesting.SampleEnvName)
   590  	c.Assert(err, gc.IsNil)
   591  
   592  	envName2 := coretesting.SampleCertName + "-2"
   593  	info2, err := store.CreateInfo(envName2)
   594  	c.Assert(err, gc.IsNil)
   595  	info2.SetBootstrapConfig(info.BootstrapConfig())
   596  	err = info2.Write()
   597  	c.Assert(err, gc.IsNil)
   598  
   599  	// Now we have info for envName2 which will actually
   600  	// cause a connection to the originally bootstrapped
   601  	// state.
   602  	apiOpen := func(*api.Info, api.DialOpts) (juju.APIState, error) {
   603  		return &mockAPIState{}, nil
   604  	}
   605  	st, err := juju.NewAPIFromStore(envName2, store, apiOpen)
   606  	c.Check(err, gc.IsNil)
   607  	st.Close()
   608  
   609  	// Sanity check that connecting to the envName2
   610  	// but with no info fails.
   611  	// Currently this panics with an "environment not prepared" error.
   612  	// Disable for now until an upcoming branch fixes it.
   613  	//	err = info2.Destroy()
   614  	//	c.Assert(err, gc.IsNil)
   615  	//	st, err = juju.NewAPIFromStore(envName2, store)
   616  	//	if err == nil {
   617  	//		st.Close()
   618  	//	}
   619  	//	c.Assert(err, gc.ErrorMatches, "fooobie")
   620  }
   621  
   622  func assertEnvironmentName(c *gc.C, client *api.Client, expectName string) {
   623  	envInfo, err := client.EnvironmentInfo()
   624  	c.Assert(err, gc.IsNil)
   625  	c.Assert(envInfo.Name, gc.Equals, expectName)
   626  }
   627  
   628  // newConfigStoreWithError that will return the given
   629  // error from ReadInfo.
   630  func newConfigStoreWithError(err error) configstore.Storage {
   631  	return &errorConfigStorage{
   632  		Storage: configstore.NewMem(),
   633  		err:     err,
   634  	}
   635  }
   636  
   637  type errorConfigStorage struct {
   638  	configstore.Storage
   639  	err error
   640  }
   641  
   642  func (store *errorConfigStorage) ReadInfo(envName string) (configstore.EnvironInfo, error) {
   643  	return nil, store.err
   644  }
   645  
   646  type environInfo struct {
   647  	creds           configstore.APICredentials
   648  	endpoint        configstore.APIEndpoint
   649  	bootstrapConfig map[string]interface{}
   650  }
   651  
   652  // newConfigStore returns a storage that contains information
   653  // for the environment name.
   654  func newConfigStore(envName string, info *environInfo) configstore.Storage {
   655  	store := configstore.NewMem()
   656  	newInfo, err := store.CreateInfo(envName)
   657  	if err != nil {
   658  		panic(err)
   659  	}
   660  	newInfo.SetAPICredentials(info.creds)
   661  	newInfo.SetAPIEndpoint(info.endpoint)
   662  	newInfo.SetBootstrapConfig(info.bootstrapConfig)
   663  	err = newInfo.Write()
   664  	if err != nil {
   665  		panic(err)
   666  	}
   667  	return store
   668  }
   669  
   670  type storageWithWriteNotify struct {
   671  	written bool
   672  	store   configstore.Storage
   673  }
   674  
   675  func (*storageWithWriteNotify) CreateInfo(envName string) (configstore.EnvironInfo, error) {
   676  	panic("CreateInfo not implemented")
   677  }
   678  
   679  func (s *storageWithWriteNotify) ReadInfo(envName string) (configstore.EnvironInfo, error) {
   680  	info, err := s.store.ReadInfo(envName)
   681  	if err != nil {
   682  		return nil, err
   683  	}
   684  	return &infoWithWriteNotify{
   685  		written:     &s.written,
   686  		EnvironInfo: info,
   687  	}, nil
   688  }
   689  
   690  type infoWithWriteNotify struct {
   691  	configstore.EnvironInfo
   692  	written *bool
   693  }
   694  
   695  func (info *infoWithWriteNotify) Write() error {
   696  	*info.written = true
   697  	return info.EnvironInfo.Write()
   698  }
   699  
   700  type APIEndpointForEnvSuite struct {
   701  	coretesting.FakeJujuHomeSuite
   702  }
   703  
   704  var _ = gc.Suite(&APIEndpointForEnvSuite{})
   705  
   706  var dummyStoreInfo = &environInfo{
   707  	creds: configstore.APICredentials{
   708  		User:     "foo",
   709  		Password: "foopass",
   710  	},
   711  	endpoint: configstore.APIEndpoint{
   712  		Addresses:   []string{"foo.invalid"},
   713  		CACert:      "certificated",
   714  		EnvironUUID: "fake-uuid",
   715  	},
   716  }
   717  
   718  func (s *APIEndpointForEnvSuite) TestAPIEndpointInStoreCached(c *gc.C) {
   719  	store := newConfigStore("env-name", dummyStoreInfo)
   720  	apiOpen := func(apiInfo *api.Info, opts api.DialOpts) (juju.APIState, error) {
   721  		return nil, nil
   722  	}
   723  	endpoint, err := juju.APIEndpointInStore("env-name", false, store, apiOpen)
   724  	c.Assert(err, gc.IsNil)
   725  	c.Check(endpoint, gc.DeepEquals, dummyStoreInfo.endpoint)
   726  }
   727  
   728  func (s *APIEndpointForEnvSuite) TestAPIEndpointForEnvSuchName(c *gc.C) {
   729  	coretesting.WriteEnvironments(c, coretesting.MultipleEnvConfig)
   730  	_, err := juju.APIEndpointForEnv("no-such-env", false)
   731  	c.Check(err, jc.Satisfies, errors.IsNotFound)
   732  	c.Check(err, gc.ErrorMatches, `environment "no-such-env" not found`)
   733  }
   734  
   735  func (s *APIEndpointForEnvSuite) TestAPIEndpointNotCached(c *gc.C) {
   736  	coretesting.WriteEnvironments(c, coretesting.MultipleEnvConfig)
   737  	store, err := configstore.Default()
   738  	c.Assert(err, gc.IsNil)
   739  	ctx := coretesting.Context(c)
   740  	env, err := environs.PrepareFromName("erewhemos", ctx, store)
   741  	c.Assert(err, gc.IsNil)
   742  	defer dummy.Reset()
   743  	envtesting.UploadFakeTools(c, env.Storage())
   744  	err = bootstrap.Bootstrap(ctx, env, environs.BootstrapParams{})
   745  	c.Assert(err, gc.IsNil)
   746  
   747  	// Note: if we get Bootstrap to start caching the API endpoint
   748  	// immediately, we'll still want to have this test for compatibility.
   749  	// We can just write blank info instead of reading and checking it is empty.
   750  	savedInfo, err := store.ReadInfo("erewhemos")
   751  	c.Assert(err, gc.IsNil)
   752  	// Ensure that the data isn't cached
   753  	c.Check(savedInfo.APIEndpoint().Addresses, gc.HasLen, 0)
   754  
   755  	called := 0
   756  	expectState := mockedAPIState(true, true)
   757  	apiOpen := func(apiInfo *api.Info, opts api.DialOpts) (juju.APIState, error) {
   758  		c.Check(apiInfo.Tag, gc.Equals, "user-admin")
   759  		c.Check(string(apiInfo.CACert), gc.Equals, coretesting.CACert)
   760  		c.Check(apiInfo.Password, gc.Equals, coretesting.DefaultMongoPassword)
   761  		c.Check(opts, gc.DeepEquals, api.DefaultDialOpts())
   762  		// we didn't know about it when connecting
   763  		c.Check(apiInfo.EnvironTag, gc.Equals, "")
   764  		called++
   765  		return expectState, nil
   766  	}
   767  	endpoint, err := juju.APIEndpointInStore("erewhemos", false, store, apiOpen)
   768  	c.Assert(err, gc.IsNil)
   769  	c.Assert(called, gc.Equals, 1)
   770  	c.Check(endpoint.Addresses, gc.DeepEquals, []string{"0.1.2.3:1234"})
   771  	c.Check(endpoint.EnvironUUID, gc.Equals, "fake-uuid")
   772  }
   773  
   774  func (s *APIEndpointForEnvSuite) TestAPIEndpointRefresh(c *gc.C) {
   775  	store := newConfigStore("env-name", dummyStoreInfo)
   776  	called := 0
   777  	expectState := mockedAPIState(true, false)
   778  	apiOpen := func(apiInfo *api.Info, opts api.DialOpts) (juju.APIState, error) {
   779  		checkCommonAPIInfoAttrs(c, apiInfo, opts)
   780  		called++
   781  		return expectState, nil
   782  	}
   783  	endpoint, err := juju.APIEndpointInStore("env-name", false, store, apiOpen)
   784  	c.Assert(err, gc.IsNil)
   785  	c.Assert(called, gc.Equals, 0)
   786  	c.Check(endpoint.Addresses, gc.DeepEquals, []string{"foo.invalid"})
   787  	// However, if we ask to refresh them, we'll connect to the API and get
   788  	// the freshest set
   789  	endpoint, err = juju.APIEndpointInStore("env-name", true, store, apiOpen)
   790  	c.Assert(err, gc.IsNil)
   791  	c.Check(called, gc.Equals, 1)
   792  	// This refresh now gives us the values return by APIHostPorts
   793  	c.Check(endpoint.Addresses, gc.DeepEquals, []string{"0.1.2.3:1234"})
   794  }
   795  
   796  func (s *APIEndpointForEnvSuite) TestAPIEndpointNotMachineLocal(c *gc.C) {
   797  	store := newConfigStore("env-name", dummyStoreInfo)
   798  	called := 0
   799  	hostPorts := [][]instance.HostPort{
   800  		instance.AddressesWithPort([]instance.Address{
   801  			instance.NewAddress("1.0.0.1", instance.NetworkPublic),
   802  			instance.NewAddress("192.0.0.1", instance.NetworkCloudLocal),
   803  			instance.NewAddress("127.0.0.1", instance.NetworkMachineLocal),
   804  			instance.NewAddress("localhost", instance.NetworkMachineLocal),
   805  		}, 1234),
   806  		instance.AddressesWithPort([]instance.Address{
   807  			instance.NewAddress("1.0.0.2", instance.NetworkUnknown),
   808  			instance.NewAddress("2002:0:0:0:0:0:100:2", instance.NetworkUnknown),
   809  			instance.NewAddress("::1", instance.NetworkMachineLocal),
   810  			instance.NewAddress("127.0.0.1", instance.NetworkMachineLocal),
   811  			instance.NewAddress("localhost", instance.NetworkMachineLocal),
   812  		}, 1235),
   813  	}
   814  
   815  	expectState := &mockAPIState{apiHostPorts: hostPorts}
   816  	apiOpen := func(_ *api.Info, _ api.DialOpts) (juju.APIState, error) {
   817  		called++
   818  		return expectState, nil
   819  	}
   820  	endpoint, err := juju.APIEndpointInStore("env-name", true, store, apiOpen)
   821  	c.Assert(err, gc.IsNil)
   822  	c.Check(called, gc.Equals, 1)
   823  	c.Check(endpoint.Addresses, gc.DeepEquals, []string{
   824  		"1.0.0.1:1234",
   825  		"192.0.0.1:1234",
   826  		"1.0.0.2:1235",
   827  	})
   828  }