~juju-qa/ubuntu/xenial/juju/xenial-2.0-beta3

« back to all changes in this revision

Viewing changes to src/github.com/juju/juju/juju/api_test.go

  • Committer: Martin Packman
  • Date: 2016-03-30 19:31:08 UTC
  • mfrom: (1.1.41)
  • Revision ID: martin.packman@canonical.com-20160330193108-h9iz3ak334uk0z5r
Merge new upstream source 2.0~beta3

Show diffs side-by-side

added added

removed removed

Lines of Context:
17
17
        gc "gopkg.in/check.v1"
18
18
 
19
19
        "github.com/juju/juju/api"
 
20
        "github.com/juju/juju/cmd/modelcmd"
20
21
        "github.com/juju/juju/environs"
21
22
        "github.com/juju/juju/environs/bootstrap"
22
23
        "github.com/juju/juju/environs/config"
23
 
        "github.com/juju/juju/environs/configstore"
24
24
        "github.com/juju/juju/environs/filestorage"
25
25
        sstesting "github.com/juju/juju/environs/simplestreams/testing"
26
26
        envtesting "github.com/juju/juju/environs/testing"
32
32
        "github.com/juju/juju/network"
33
33
        "github.com/juju/juju/provider/dummy"
34
34
        coretesting "github.com/juju/juju/testing"
35
 
        "github.com/juju/juju/version"
 
35
        jujuversion "github.com/juju/juju/version"
36
36
)
37
37
 
38
38
type NewAPIClientSuite struct {
82
82
        cs.FakeJujuXDGDataHomeSuite.TearDownTest(c)
83
83
}
84
84
 
85
 
func (s *NewAPIClientSuite) bootstrapEnv(c *gc.C) (configstore.Storage, jujuclient.ClientStore) {
 
85
func (s *NewAPIClientSuite) bootstrapModel(c *gc.C) (environs.Environ, jujuclient.ClientStore) {
86
86
        const controllerName = "local.my-controller"
87
87
 
88
 
        legacyStore := configstore.NewMem()
89
88
        store := jujuclienttesting.NewMemStore()
90
89
 
91
90
        ctx := envtesting.BootstrapContext(c)
92
 
        cfg, err := config.New(config.UseDefaults, dummy.SampleConfig())
93
 
        c.Assert(err, jc.ErrorIsNil)
94
91
 
95
 
        c.Assert(err, jc.ErrorIsNil)
96
 
        env, err := environs.Prepare(ctx, legacyStore, store, controllerName, environs.PrepareForBootstrapParams{Config: cfg})
 
92
        env, err := environs.Prepare(ctx, store, environs.PrepareParams{
 
93
                ControllerName: controllerName,
 
94
                BaseConfig:     dummy.SampleConfig(),
 
95
                CloudName:      "dummy",
 
96
        })
97
97
        c.Assert(err, jc.ErrorIsNil)
98
98
 
99
99
        storageDir := c.MkDir()
104
104
 
105
105
        err = bootstrap.Bootstrap(ctx, env, bootstrap.BootstrapParams{})
106
106
        c.Assert(err, jc.ErrorIsNil)
107
 
        return legacyStore, store
 
107
 
 
108
        return env, store
108
109
}
109
110
 
110
 
func (s *NewAPIClientSuite) TestWithInfoOnly(c *gc.C) {
111
 
        legacyStore := newConfigStore("noconfig", dummyStoreInfo)
112
 
        store := newClientStore(c, "noconfig", dummyStoreInfo)
 
111
func (s *NewAPIClientSuite) TestWithBootstrapConfig(c *gc.C) {
 
112
        store := newClientStore(c, "noconfig")
113
113
 
114
114
        called := 0
115
115
        expectState := mockedAPIState(mockedHostPort | mockedModelTag)
120
120
                return expectState, nil
121
121
        }
122
122
 
123
 
        // Give NewAPIFromStore a store interface that can report when the
124
 
        // config was written to, to check if the cache is updated.
125
 
        mockStore := &storageWithWriteNotify{store: legacyStore}
126
 
        st, err := juju.NewAPIFromStore("noconfig", "admin@local", "noconfig", mockStore, store, apiOpen)
 
123
        st, err := newAPIConnectionFromNames(c, "noconfig", "admin@local", "admin", store, apiOpen, noBootstrapConfig)
127
124
        c.Assert(err, jc.ErrorIsNil)
128
125
        c.Assert(st, gc.Equals, expectState)
129
126
        c.Assert(called, gc.Equals, 1)
130
 
        c.Assert(mockStore.written, jc.IsTrue)
131
 
        info, err := legacyStore.ReadInfo("noconfig:noconfig")
132
 
        c.Assert(err, jc.ErrorIsNil)
133
 
        ep := info.APIEndpoint()
134
 
        c.Check(ep.Addresses, jc.DeepEquals, []string{
135
 
                "0.1.2.3:1234", "[2001:db8::1]:1234",
136
 
        })
137
 
        c.Check(ep.ModelUUID, gc.Equals, fakeUUID)
138
 
        mockStore.written = false
 
127
        // The addresses should have been updated.
 
128
        c.Assert(
 
129
                store.Controllers["noconfig"].APIEndpoints,
 
130
                jc.DeepEquals,
 
131
                []string{"0.1.2.3:1234", "[2001:db8::1]:1234"},
 
132
        )
139
133
 
140
134
        controllerBefore, err := store.ControllerByName("noconfig")
141
135
        c.Assert(err, jc.ErrorIsNil)
142
136
 
143
137
        // If APIHostPorts haven't changed, then the store won't be updated.
144
 
        st, err = juju.NewAPIFromStore("noconfig", "admin@local", "noconfig", mockStore, store, apiOpen)
 
138
        stubStore := jujuclienttesting.WrapClientStore(store)
 
139
        st, err = newAPIConnectionFromNames(c, "noconfig", "admin@local", "admin", stubStore, apiOpen, noBootstrapConfig)
145
140
        c.Assert(err, jc.ErrorIsNil)
146
141
        c.Assert(st, gc.Equals, expectState)
147
142
        c.Assert(called, gc.Equals, 2)
148
 
        c.Assert(mockStore.written, jc.IsFalse)
 
143
        stubStore.CheckCallNames(c, "AccountByName", "ModelByName", "ControllerByName")
149
144
 
150
145
        controllerAfter, err := store.ControllerByName("noconfig")
151
146
        c.Assert(err, jc.ErrorIsNil)
153
148
}
154
149
 
155
150
func (s *NewAPIClientSuite) TestWithInfoError(c *gc.C) {
 
151
        store := newClientStore(c, "noconfig")
 
152
        err := store.UpdateController("noconfig", jujuclient.ControllerDetails{
 
153
                ControllerUUID: fakeUUID,
 
154
                CACert:         "certificate",
 
155
        })
 
156
        c.Assert(err, jc.ErrorIsNil)
 
157
 
156
158
        expectErr := fmt.Errorf("an error")
157
 
        legacyStore := newConfigStoreWithError(expectErr)
158
 
        store := newClientStore(c, "noconfig", &environInfo{
159
 
                endpoint: configstore.APIEndpoint{
160
 
                        ServerUUID: "valid.uuid",
161
 
                        CACert:     "certificated",
162
 
                },
163
 
        })
164
 
        client, err := juju.NewAPIFromStore("noconfig", "", "", legacyStore, store, panicAPIOpen)
 
159
        getBootstrapConfig := func(string) (*config.Config, error) {
 
160
                return nil, expectErr
 
161
        }
 
162
 
 
163
        client, err := newAPIConnectionFromNames(c, "noconfig", "", "", store, panicAPIOpen, getBootstrapConfig)
165
164
        c.Assert(errors.Cause(err), gc.Equals, expectErr)
166
165
        c.Assert(client, gc.IsNil)
167
166
}
168
167
 
169
168
func (s *NewAPIClientSuite) TestWithInfoNoAddresses(c *gc.C) {
170
 
        store := newConfigStore("noconfig", &environInfo{
171
 
                endpoint: configstore.APIEndpoint{
172
 
                        Addresses: []string{},
173
 
                        CACert:    "certificated",
174
 
                },
175
 
        })
176
 
        cache := newClientStore(c, "noconfig", &environInfo{
177
 
                endpoint: configstore.APIEndpoint{
178
 
                        Addresses:  []string{},
179
 
                        ServerUUID: "valid.uuid",
180
 
                        ModelUUID:  "valid.uuid",
181
 
                        CACert:     "certificated",
182
 
                },
183
 
        })
184
 
        st, err := juju.NewAPIFromStore("noconfig", "admin@local", "noconfig", store, cache, panicAPIOpen)
185
 
        c.Assert(err, gc.ErrorMatches, "bootstrap config not found")
 
169
        store := newClientStore(c, "noconfig")
 
170
        err := store.UpdateController("noconfig", jujuclient.ControllerDetails{
 
171
                ControllerUUID: fakeUUID,
 
172
                CACert:         "certificate",
 
173
        })
 
174
        c.Assert(err, jc.ErrorIsNil)
 
175
 
 
176
        st, err := newAPIConnectionFromNames(c, "noconfig", "admin@local", "", store, panicAPIOpen, noBootstrapConfig)
 
177
        c.Assert(err, gc.ErrorMatches, "bootstrap config for controller noconfig not found")
186
178
        c.Assert(st, gc.IsNil)
187
179
}
188
180
 
189
 
var noTagStoreInfo = &environInfo{
190
 
        creds: configstore.APICredentials{
191
 
                User:     "admin@local",
192
 
                Password: "hunter2",
193
 
        },
194
 
        endpoint: configstore.APIEndpoint{
195
 
                Addresses: []string{"foo.invalid"},
196
 
                CACert:    "certificated",
197
 
        },
198
 
}
199
 
 
200
181
type mockedStateFlags int
201
182
 
202
183
const (
203
 
        noFlags          mockedStateFlags = 0x0000
204
 
        mockedHostPort   mockedStateFlags = 0x0001
205
 
        mockedModelTag   mockedStateFlags = 0x0002
206
 
        mockedPreferIPv6 mockedStateFlags = 0x0004
 
184
        noFlags        mockedStateFlags = 0x0000
 
185
        mockedHostPort mockedStateFlags = 0x0001
 
186
        mockedModelTag mockedStateFlags = 0x0002
207
187
)
208
188
 
209
189
func mockedAPIState(flags mockedStateFlags) *mockAPIState {
210
190
        hasHostPort := flags&mockedHostPort == mockedHostPort
211
191
        hasModelTag := flags&mockedModelTag == mockedModelTag
212
 
        preferIPv6 := flags&mockedPreferIPv6 == mockedPreferIPv6
213
192
        addr := ""
214
193
 
215
194
        apiHostPorts := [][]network.HostPort{}
217
196
                var apiAddrs []network.Address
218
197
                ipv4Address := network.NewAddress("0.1.2.3")
219
198
                ipv6Address := network.NewAddress("2001:db8::1")
220
 
                if preferIPv6 {
221
 
                        addr = net.JoinHostPort(ipv6Address.Value, "1234")
222
 
                        apiAddrs = append(apiAddrs, ipv6Address, ipv4Address)
223
 
                } else {
224
 
                        addr = net.JoinHostPort(ipv4Address.Value, "1234")
225
 
                        apiAddrs = append(apiAddrs, ipv4Address, ipv6Address)
226
 
                }
 
199
                addr = net.JoinHostPort(ipv4Address.Value, "1234")
 
200
                apiAddrs = append(apiAddrs, ipv4Address, ipv6Address)
227
201
                apiHostPorts = [][]network.HostPort{
228
202
                        network.AddressesWithPort(apiAddrs, 1234),
229
203
                }
242
216
 
243
217
func checkCommonAPIInfoAttrs(c *gc.C, apiInfo *api.Info, opts api.DialOpts) {
244
218
        c.Check(apiInfo.Tag, gc.Equals, names.NewUserTag("admin@local"))
245
 
        c.Check(string(apiInfo.CACert), gc.Equals, "certificated")
 
219
        c.Check(string(apiInfo.CACert), gc.Equals, "certificate")
246
220
        c.Check(apiInfo.Password, gc.Equals, "hunter2")
247
221
        c.Check(opts, gc.DeepEquals, api.DefaultDialOpts())
248
222
}
249
223
 
250
 
func (s *NewAPIClientSuite) TestWithInfoNoAPIHostports(c *gc.C) {
251
 
        // The API doesn't have apiHostPorts, we don't want to
252
 
        // override the local cache with bad endpoints.
253
 
        legacyStore := newConfigStore("noconfig", noTagStoreInfo)
254
 
        store := newClientStore(c, "noconfig", dummyStoreInfo)
255
 
 
256
 
        called := 0
257
 
        expectState := mockedAPIState(mockedModelTag | mockedPreferIPv6)
258
 
        apiOpen := func(apiInfo *api.Info, opts api.DialOpts) (api.Connection, error) {
259
 
                checkCommonAPIInfoAttrs(c, apiInfo, opts)
260
 
                c.Check(apiInfo.ModelTag.Id(), gc.Equals, "")
261
 
                called++
262
 
                return expectState, nil
263
 
        }
264
 
 
265
 
        mockStore := &storageWithWriteNotify{store: legacyStore}
266
 
        st, err := juju.NewAPIFromStore("noconfig", "admin@local", "", mockStore, store, apiOpen)
267
 
        c.Assert(err, jc.ErrorIsNil)
268
 
        c.Assert(st, gc.Equals, expectState)
269
 
        c.Assert(called, gc.Equals, 1)
270
 
 
271
 
        // We should not have disturbed the Addresses
272
 
 
273
 
        details, err := store.ControllerByName("noconfig")
274
 
        c.Assert(err, jc.ErrorIsNil)
275
 
        c.Check(details.APIEndpoints, gc.HasLen, 1)
276
 
        c.Check(details.APIEndpoints[0], gc.Matches, `foo\.invalid`)
277
 
 
278
 
        info, err := legacyStore.ReadInfo("noconfig:noconfig")
279
 
        c.Assert(err, jc.ErrorIsNil)
280
 
        ep := info.APIEndpoint()
281
 
        c.Check(ep.Addresses, gc.HasLen, 1)
282
 
        c.Check(ep.Addresses[0], gc.Matches, `foo\.invalid`)
283
 
}
284
 
 
285
224
func (s *NewAPIClientSuite) TestWithInfoAPIOpenError(c *gc.C) {
286
 
        store := newConfigStore("noconfig", &environInfo{
287
 
                endpoint: configstore.APIEndpoint{
288
 
                        Addresses: []string{"foo.invalid"},
289
 
                },
290
 
        })
291
 
        jujuClient := newClientStore(c, "noconfig", &environInfo{
292
 
                endpoint: configstore.APIEndpoint{
293
 
                        Addresses:  []string{"foo.invalid"},
294
 
                        ServerUUID: "some.uuid",
295
 
                        CACert:     "some.cert",
296
 
                },
297
 
        })
 
225
        jujuClient := newClientStore(c, "noconfig")
298
226
 
299
227
        apiOpen := func(apiInfo *api.Info, opts api.DialOpts) (api.Connection, error) {
300
228
                return nil, errors.Errorf("an error")
301
229
        }
302
 
        st, err := juju.NewAPIFromStore("noconfig", "", "", store, jujuClient, apiOpen)
303
 
        // We expect to  get the isNotFound error as it is more important than the
304
 
        // infoConnectError "an error"
305
 
        c.Assert(err, gc.ErrorMatches, "bootstrap config not found")
 
230
        st, err := newAPIConnectionFromNames(c, "noconfig", "", "", jujuClient, apiOpen, noBootstrapConfig)
 
231
        // We expect to get the error from apiOpen, because it is not
 
232
        // fatal to have no bootstrap config.
 
233
        c.Assert(err, gc.ErrorMatches, "connecting with cached addresses: an error")
306
234
        c.Assert(st, gc.IsNil)
307
235
}
308
236
 
309
237
func (s *NewAPIClientSuite) TestWithSlowInfoConnect(c *gc.C) {
310
238
        c.Skip("wallyworld - this is a dumb test relying on an arbitary 50ms delay to pass")
311
 
        s.PatchValue(&version.Current, coretesting.FakeVersionNumber)
312
 
        legacyStore, store := s.bootstrapEnv(c)
 
239
        s.PatchValue(&jujuversion.Current, coretesting.FakeVersionNumber)
 
240
        _, store := s.bootstrapModel(c)
313
241
        setEndpointAddressAndHostname(c, store, "0.1.2.3", "infoapi.invalid")
314
242
 
315
243
        infoOpenedState := mockedAPIState(noFlags)
335
263
        cfgOpenedState.close = infoOpenedState.close
336
264
 
337
265
        startTime := time.Now()
338
 
        st, err := juju.NewAPIFromStore("local.my-controller", "admin@local", "only", legacyStore, store, apiOpen)
 
266
        st, err := newAPIConnectionFromNames(c,
 
267
                "local.my-controller", "admin@local", "only", store, apiOpen,
 
268
                modelcmd.NewGetBootstrapConfigFunc(store),
 
269
        )
339
270
        c.Assert(err, jc.ErrorIsNil)
340
271
        // The connection logic should wait for some time before opening
341
272
        // the API from the configuration.
357
288
        }
358
289
}
359
290
 
360
 
func (s *NewAPIClientSuite) TestGetBootstrapConfigNoLegacyInfo(c *gc.C) {
361
 
        store := configstore.NewMem()
362
 
        cfg, err := juju.GetBootstrapConfig(store, "whatever", "")
363
 
        c.Assert(err, gc.ErrorMatches, `getting controller info: model "whatever:whatever" not found`)
364
 
        c.Assert(cfg, gc.IsNil)
365
 
}
366
 
 
367
 
func (s *NewAPIClientSuite) TestGetBootstrapConfigNoBootstrapConfig(c *gc.C) {
368
 
        store := configstore.NewMem()
369
 
        info := store.CreateInfo("whatever:whatever")
370
 
        err := info.Write()
371
 
        c.Assert(err, jc.ErrorIsNil)
372
 
        cfg, err := juju.GetBootstrapConfig(store, "whatever", "")
373
 
        c.Assert(err, gc.ErrorMatches, "bootstrap config not found")
374
 
        c.Assert(cfg, gc.IsNil)
375
 
}
376
 
 
377
 
func (s *NewAPIClientSuite) TestGetBootstrapConfigBadConfigDoesntPanic(c *gc.C) {
378
 
        store := configstore.NewMem()
379
 
        info := store.CreateInfo("whatever:whatever")
380
 
        info.SetBootstrapConfig(map[string]interface{}{"something": "else"})
381
 
        err := info.Write()
382
 
        c.Assert(err, jc.ErrorIsNil)
383
 
        cfg, err := juju.GetBootstrapConfig(store, "whatever", "")
384
 
        // The specific error we get depends on what key is invalid, which is a
385
 
        // bit spurious, but what we care about is that we didn't get a panic,
386
 
        // but instead got an error
387
 
        c.Assert(err, gc.ErrorMatches, ".*expected.*got nothing")
388
 
        c.Assert(cfg, gc.IsNil)
389
 
}
390
 
 
391
291
func setEndpointAddressAndHostname(c *gc.C, store jujuclient.ControllerStore, addr, host string) {
392
292
        // Populate the controller details with known address and hostname.
393
293
        details, err := store.ControllerByName("local.my-controller")
394
294
        c.Assert(err, jc.ErrorIsNil)
395
295
        details.APIEndpoints = []string{addr}
396
 
        details.Servers = []string{host}
 
296
        details.UnresolvedAPIEndpoints = []string{host}
397
297
        err = store.UpdateController("local.my-controller", *details)
398
298
        c.Assert(err, jc.ErrorIsNil)
399
299
}
400
300
 
401
301
func (s *NewAPIClientSuite) TestWithSlowConfigConnect(c *gc.C) {
402
 
        s.PatchValue(&version.Current, coretesting.FakeVersionNumber)
 
302
        s.PatchValue(&jujuversion.Current, coretesting.FakeVersionNumber)
403
303
 
404
 
        legacyStore, store := s.bootstrapEnv(c)
 
304
        _, store := s.bootstrapModel(c)
405
305
        setEndpointAddressAndHostname(c, store, "0.1.2.3", "infoapi.invalid")
406
306
 
407
307
        infoOpenedState := mockedAPIState(noFlags)
430
330
 
431
331
        done := make(chan struct{})
432
332
        go func() {
433
 
                st, err := juju.NewAPIFromStore("local.my-controller", "admin@local", "only", legacyStore, store, apiOpen)
 
333
                st, err := newAPIConnectionFromNames(c,
 
334
                        "local.my-controller", "admin@local", "only", store, apiOpen,
 
335
                        modelcmd.NewGetBootstrapConfigFunc(store),
 
336
                )
434
337
                c.Check(err, jc.ErrorIsNil)
435
338
                c.Check(st, gc.Equals, infoOpenedState)
436
339
                close(done)
468
371
}
469
372
 
470
373
func (s *NewAPIClientSuite) TestBothError(c *gc.C) {
471
 
        s.PatchValue(&version.Current, coretesting.FakeVersionNumber)
472
 
        legacyStore, store := s.bootstrapEnv(c)
 
374
        s.PatchValue(&jujuversion.Current, coretesting.FakeVersionNumber)
 
375
        env, store := s.bootstrapModel(c)
473
376
        setEndpointAddressAndHostname(c, store, "0.1.2.3", "infoapi.invalid")
474
377
 
 
378
        getBootstrapConfig := func(string) (*config.Config, error) {
 
379
                return env.Config(), nil
 
380
        }
 
381
 
475
382
        s.PatchValue(juju.ProviderConnectDelay, 0*time.Second)
476
383
        apiOpen := func(info *api.Info, opts api.DialOpts) (api.Connection, error) {
477
384
                if info.Addrs[0] == "infoapi.invalid" {
479
386
                }
480
387
                return nil, fmt.Errorf("config connect failed")
481
388
        }
482
 
        st, err := juju.NewAPIFromStore("local.my-controller", "admin@local", "only", legacyStore, store, apiOpen)
483
 
        c.Check(err, gc.ErrorMatches, "config connect failed")
 
389
        st, err := newAPIConnectionFromNames(c, "local.my-controller", "admin@local", "only", store, apiOpen, getBootstrapConfig)
 
390
        c.Check(err, gc.ErrorMatches, "connecting with bootstrap config: config connect failed")
484
391
        c.Check(st, gc.IsNil)
485
392
}
486
393
 
487
 
func defaultConfigStore(c *gc.C) configstore.Storage {
488
 
        store, err := configstore.Default()
489
 
        c.Assert(err, jc.ErrorIsNil)
490
 
        return store
491
 
}
492
 
 
493
 
func (s *NewAPIClientSuite) TestWithBootstrapConfigAndNoEnvironmentsFile(c *gc.C) {
494
 
        s.PatchValue(&version.Current, coretesting.FakeVersionNumber)
495
 
        legacyStore, store := s.bootstrapEnv(c)
496
 
 
497
 
        details, err := store.ControllerByName("local.my-controller")
498
 
        c.Assert(err, jc.ErrorIsNil)
499
 
        c.Assert(details.APIEndpoints, gc.HasLen, 0)
500
 
 
501
 
        info, err := legacyStore.ReadInfo("local.my-controller:only")
502
 
        c.Assert(err, jc.ErrorIsNil)
503
 
        c.Assert(info.BootstrapConfig(), gc.NotNil)
504
 
        c.Assert(info.APIEndpoint().Addresses, gc.HasLen, 0)
505
 
 
506
 
        apiOpen := func(*api.Info, api.DialOpts) (api.Connection, error) {
507
 
                return mockedAPIState(noFlags), nil
508
 
        }
509
 
        st, err := juju.NewAPIFromStore("local.my-controller", "admin@local", "only", legacyStore, store, apiOpen)
510
 
        c.Check(err, jc.ErrorIsNil)
511
 
        st.Close()
512
 
}
513
 
 
514
 
// newConfigStoreWithError that will return the given
515
 
// error from ReadInfo.
516
 
func newConfigStoreWithError(err error) configstore.Storage {
517
 
        return &errorConfigStorage{
518
 
                Storage: configstore.NewMem(),
519
 
                err:     err,
520
 
        }
521
 
}
522
 
 
523
 
type errorConfigStorage struct {
524
 
        configstore.Storage
525
 
        err error
526
 
}
527
 
 
528
 
func (store *errorConfigStorage) ReadInfo(envName string) (configstore.EnvironInfo, error) {
529
 
        return nil, store.err
530
 
}
531
 
 
532
 
type environInfo struct {
533
 
        creds           configstore.APICredentials
534
 
        endpoint        configstore.APIEndpoint
535
 
        bootstrapConfig map[string]interface{}
536
 
}
537
 
 
538
 
// newConfigStore returns a storage that contains information
539
 
// for the environment name.
540
 
func newConfigStore(envName string, info *environInfo) configstore.Storage {
541
 
        store := configstore.NewMem()
542
 
        newInfo := store.CreateInfo(envName + ":" + envName)
543
 
        newInfo.SetAPICredentials(info.creds)
544
 
        newInfo.SetAPIEndpoint(info.endpoint)
545
 
        newInfo.SetBootstrapConfig(info.bootstrapConfig)
546
 
        err := newInfo.Write()
547
 
        if err != nil {
548
 
                panic(err)
549
 
        }
550
 
        return store
551
 
}
552
 
 
553
394
// newClientStore returns a client store that contains information
554
395
// based on the given controller namd and info.
555
 
func newClientStore(c *gc.C, controllerName string, info *environInfo) jujuclient.ClientStore {
 
396
func newClientStore(c *gc.C, controllerName string) *jujuclienttesting.MemStore {
556
397
        store := jujuclienttesting.NewMemStore()
557
398
        err := store.UpdateController(controllerName, jujuclient.ControllerDetails{
558
 
                info.endpoint.Hostnames,
559
 
                info.endpoint.ServerUUID,
560
 
                info.endpoint.Addresses,
561
 
                info.endpoint.CACert,
562
 
        })
563
 
        c.Assert(err, jc.ErrorIsNil)
564
 
 
565
 
        if info.endpoint.ModelUUID != "" {
566
 
                err = store.UpdateModel(controllerName, "admin@local", controllerName, jujuclient.ModelDetails{
567
 
                        info.endpoint.ModelUUID,
568
 
                })
569
 
                c.Assert(err, jc.ErrorIsNil)
570
 
 
571
 
                // Models belong to accounts, so we must have an account even
572
 
                // if "creds" is not initialised. If it is, it may overwrite
573
 
                // this one.
574
 
                err = store.UpdateAccount(controllerName, "admin@local", jujuclient.AccountDetails{
575
 
                        User: "admin@local",
576
 
                })
577
 
                c.Assert(err, jc.ErrorIsNil)
578
 
                err = store.SetCurrentAccount(controllerName, "admin@local")
579
 
                c.Assert(err, jc.ErrorIsNil)
580
 
        }
581
 
 
582
 
        if info.creds.User != "" {
583
 
                user := names.NewUserTag(info.creds.User).Canonical()
584
 
                err = store.UpdateAccount(controllerName, user, jujuclient.AccountDetails{
585
 
                        User:     user,
586
 
                        Password: info.creds.Password,
587
 
                })
588
 
                c.Assert(err, jc.ErrorIsNil)
589
 
                err = store.SetCurrentAccount(controllerName, user)
590
 
                c.Assert(err, jc.ErrorIsNil)
591
 
        }
592
 
 
 
399
                ControllerUUID: fakeUUID,
 
400
                CACert:         "certificate",
 
401
                APIEndpoints:   []string{"foo.invalid"},
 
402
        })
 
403
        c.Assert(err, jc.ErrorIsNil)
 
404
 
 
405
        err = store.UpdateModel(controllerName, "admin@local", "admin", jujuclient.ModelDetails{
 
406
                fakeUUID,
 
407
        })
 
408
        c.Assert(err, jc.ErrorIsNil)
 
409
 
 
410
        // Models belong to accounts, so we must have an account even
 
411
        // if "creds" is not initialised. If it is, it may overwrite
 
412
        // this one.
 
413
        err = store.UpdateAccount(controllerName, "admin@local", jujuclient.AccountDetails{
 
414
                User:     "admin@local",
 
415
                Password: "hunter2",
 
416
        })
 
417
        c.Assert(err, jc.ErrorIsNil)
 
418
        err = store.SetCurrentAccount(controllerName, "admin@local")
 
419
        c.Assert(err, jc.ErrorIsNil)
593
420
        return store
594
421
}
595
422
 
596
 
type storageWithWriteNotify struct {
597
 
        written bool
598
 
        store   configstore.Storage
599
 
}
600
 
 
601
 
func (*storageWithWriteNotify) CreateInfo(envName string) configstore.EnvironInfo {
602
 
        panic("CreateInfo not implemented")
603
 
}
604
 
 
605
 
func (*storageWithWriteNotify) List() ([]string, error) {
606
 
        return nil, nil
607
 
}
608
 
 
609
 
func (*storageWithWriteNotify) ListSystems() ([]string, error) {
610
 
        return []string{"noconfig:noconfig"}, nil
611
 
}
612
 
 
613
 
func (s *storageWithWriteNotify) ReadInfo(envName string) (configstore.EnvironInfo, error) {
614
 
        info, err := s.store.ReadInfo(envName)
615
 
        if err != nil {
616
 
                return nil, err
617
 
        }
618
 
        return &infoWithWriteNotify{
619
 
                written:     &s.written,
620
 
                EnvironInfo: info,
621
 
        }, nil
622
 
}
623
 
 
624
 
type infoWithWriteNotify struct {
625
 
        configstore.EnvironInfo
626
 
        written *bool
627
 
}
628
 
 
629
 
func (info *infoWithWriteNotify) Write() error {
630
 
        *info.written = true
631
 
        return info.EnvironInfo.Write()
632
 
}
633
 
 
634
423
type CacheAPIEndpointsSuite struct {
635
424
        jujutesting.JujuConnSuite
636
425
 
637
426
        hostPorts   [][]network.HostPort
638
427
        modelTag    names.ModelTag
639
428
        apiHostPort network.HostPort
640
 
        store       configstore.Storage
641
429
 
642
430
        resolveSeq      int
643
431
        resolveNumCalls int
648
436
var _ = gc.Suite(&CacheAPIEndpointsSuite{})
649
437
 
650
438
func (s *CacheAPIEndpointsSuite) SetUpTest(c *gc.C) {
651
 
        s.PatchValue(juju.ResolveOrDropHostnames, s.mockResolveOrDropHostnames)
652
 
 
653
439
        s.hostPorts = [][]network.HostPort{
654
440
                network.NewHostPorts(1234,
655
441
                        "1.0.0.1",
682
468
        s.resolveNumCalls = 0
683
469
        s.numResolved = 0
684
470
        s.modelTag = names.NewModelTag(fakeUUID)
685
 
        s.store = configstore.NewMem()
686
471
 
687
472
        s.JujuConnSuite.SetUpTest(c)
 
473
        s.PatchValue(juju.ResolveOrDropHostnames, s.mockResolveOrDropHostnames)
688
474
 
689
475
        apiHostPort, err := network.ParseHostPorts(s.APIState.Addr())
690
476
        c.Assert(err, jc.ErrorIsNil)
691
477
        s.apiHostPort = apiHostPort[0]
692
478
}
693
479
 
694
 
func (s *CacheAPIEndpointsSuite) assertCreateInfo(c *gc.C, name string) configstore.EnvironInfo {
695
 
        info := s.store.CreateInfo(name)
696
 
 
697
 
        // info should have server uuid.
698
 
        updateEndpoint := info.APIEndpoint()
699
 
        updateEndpoint.ServerUUID = fakeUUID
700
 
        info.SetAPIEndpoint(updateEndpoint)
701
 
 
 
480
func (s *CacheAPIEndpointsSuite) assertCreateController(c *gc.C, name string) jujuclient.ControllerDetails {
702
481
        // write controller
703
 
        c.Assert(updateEndpoint.Hostnames, gc.HasLen, 0)
704
 
        c.Assert(updateEndpoint.Addresses, gc.HasLen, 0)
705
482
        controllerDetails := jujuclient.ControllerDetails{
706
 
                updateEndpoint.Hostnames,
707
 
                fakeUUID,
708
 
                updateEndpoint.Addresses,
709
 
                "this.is.ca.cert.but.not.relevant.slash.used.in.this.test",
 
483
                ControllerUUID: fakeUUID,
 
484
                CACert:         "certificate",
710
485
        }
711
486
        err := s.ControllerStore.UpdateController(name, controllerDetails)
712
487
        c.Assert(err, jc.ErrorIsNil)
713
 
        return info
 
488
        return controllerDetails
714
489
}
715
490
 
716
491
func (s *CacheAPIEndpointsSuite) assertControllerDetailsUpdated(c *gc.C, name string, check gc.Checker) {
717
492
        found, err := s.ControllerStore.ControllerByName(name)
718
493
        c.Assert(err, jc.ErrorIsNil)
719
 
        c.Assert(found.Servers, check, 0)
 
494
        c.Assert(found.UnresolvedAPIEndpoints, check, 0)
720
495
        c.Assert(found.APIEndpoints, check, 0)
721
496
}
722
497
 
728
503
        s.assertControllerDetailsUpdated(c, name, gc.HasLen)
729
504
}
730
505
 
731
 
func (s *CacheAPIEndpointsSuite) TestPrepareEndpointsForCachingPreferIPv6True(c *gc.C) {
732
 
        s.PatchValue(juju.MaybePreferIPv6, func(_ configstore.EnvironInfo) bool {
733
 
                return true
734
 
        })
735
 
 
736
 
        info := s.assertCreateInfo(c, "controller-name1")
737
 
        err := info.Write()
738
 
        c.Assert(err, jc.ErrorIsNil)
739
 
        err = juju.UpdateControllerAddresses(s.ControllerStore, s.store, "controller-name1", s.hostPorts, s.apiHostPort)
740
 
        c.Assert(err, jc.ErrorIsNil)
741
 
        info, err = s.store.ReadInfo("controller-name1")
742
 
        c.Assert(err, jc.ErrorIsNil)
743
 
        s.assertEndpointsPreferIPv6True(c, info)
744
 
        s.assertControllerUpdated(c, "controller-name1")
745
 
}
746
 
 
747
 
func (s *CacheAPIEndpointsSuite) TestPrepareEndpointsForCachingPreferIPv6False(c *gc.C) {
748
 
        s.PatchValue(juju.MaybePreferIPv6, func(_ configstore.EnvironInfo) bool {
749
 
                return false
750
 
        })
751
 
        info := s.assertCreateInfo(c, "controller-name1")
752
 
        err := info.Write()
753
 
        c.Assert(err, jc.ErrorIsNil)
754
 
        err = juju.UpdateControllerAddresses(s.ControllerStore, s.store, "controller-name1", s.hostPorts, s.apiHostPort)
755
 
        c.Assert(err, jc.ErrorIsNil)
756
 
        info, err = s.store.ReadInfo("controller-name1")
757
 
        c.Assert(err, jc.ErrorIsNil)
758
 
        s.assertEndpointsPreferIPv6False(c, info)
 
506
func (s *CacheAPIEndpointsSuite) TestPrepareEndpointsForCaching(c *gc.C) {
 
507
        s.assertCreateController(c, "controller-name1")
 
508
        err := juju.UpdateControllerAddresses(s.ControllerStore, "controller-name1", s.hostPorts, s.apiHostPort)
 
509
        c.Assert(err, jc.ErrorIsNil)
 
510
        controllerDetails, err := s.ControllerStore.ControllerByName("controller-name1")
 
511
        c.Assert(err, jc.ErrorIsNil)
 
512
        s.assertEndpoints(c, controllerDetails)
759
513
        s.assertControllerUpdated(c, "controller-name1")
760
514
}
761
515
 
763
517
        // Test that if new endpoints hostnames are the same as the
764
518
        // cached, no DNS resolution happens (i.e. we don't resolve on
765
519
        // every connection, but as needed).
766
 
        info := s.store.CreateInfo("controller-name")
767
520
        hps := network.NewHostPorts(1234,
768
521
                "8.8.8.8",
769
522
                "example.com",
770
523
                "10.0.0.1",
771
524
        )
772
 
        info.SetAPIEndpoint(configstore.APIEndpoint{
773
 
                Hostnames: network.HostPortsToStrings(hps),
774
 
        })
775
 
        err := info.Write()
 
525
        controllerDetails := jujuclient.ControllerDetails{
 
526
                ControllerUUID:         fakeUUID,
 
527
                CACert:                 "certificate",
 
528
                UnresolvedAPIEndpoints: network.HostPortsToStrings(hps),
 
529
        }
 
530
        err := s.ControllerStore.UpdateController("controller-name", controllerDetails)
776
531
        c.Assert(err, jc.ErrorIsNil)
777
532
 
778
533
        addrs, hosts, changed := juju.PrepareEndpointsForCaching(
779
 
                info, [][]network.HostPort{hps},
 
534
                controllerDetails, [][]network.HostPort{hps},
780
535
        )
781
536
        c.Assert(addrs, gc.IsNil)
782
537
        c.Assert(hosts, gc.IsNil)
793
548
        // Test that if new endpoints hostnames are different than the
794
549
        // cached hostnames DNS resolution happens and we compare resolved
795
550
        // addresses.
796
 
        info := s.store.CreateInfo("controller-name")
797
551
        // Because Hostnames are sorted before caching, reordering them
798
552
        // will simulate they have changed.
799
553
        unsortedHPs := network.NewHostPorts(1234,
817
571
                "fc00::2", // from ipv6.example.com
818
572
        )
819
573
        strResolved := network.HostPortsToStrings(resolvedHPs)
820
 
        info.SetAPIEndpoint(configstore.APIEndpoint{
821
 
                Hostnames: strUnsorted,
822
 
        })
823
 
        err := info.Write()
 
574
        controllerDetails := jujuclient.ControllerDetails{
 
575
                ControllerUUID:         fakeUUID,
 
576
                CACert:                 "certificate",
 
577
                UnresolvedAPIEndpoints: strUnsorted,
 
578
        }
 
579
        err := s.ControllerStore.UpdateController("controller-name", controllerDetails)
824
580
        c.Assert(err, jc.ErrorIsNil)
825
581
 
826
582
        addrs, hosts, changed := juju.PrepareEndpointsForCaching(
827
 
                info, [][]network.HostPort{unsortedHPs},
 
583
                controllerDetails, [][]network.HostPort{unsortedHPs},
828
584
        )
829
585
        c.Assert(addrs, jc.DeepEquals, strResolved)
830
586
        c.Assert(hosts, jc.DeepEquals, strSorted)
841
597
        // Test that if new endpoints hostnames are different than the
842
598
        // cached hostnames, but after resolving the addresses match the
843
599
        // cached addresses, the cache is not changed.
844
 
        info := s.store.CreateInfo("controller-name")
 
600
 
845
601
        // Because Hostnames are sorted before caching, reordering them
846
602
        // will simulate they have changed.
847
603
        unsortedHPs := network.NewHostPorts(1234,
864
620
                "fc00::2", // from ipv6.example.com
865
621
        )
866
622
        strResolved := network.HostPortsToStrings(resolvedHPs)
867
 
        info.SetAPIEndpoint(configstore.APIEndpoint{
868
 
                Hostnames: strUnsorted,
869
 
                Addresses: strResolved,
870
 
        })
871
 
        err := info.Write()
 
623
        controllerDetails := jujuclient.ControllerDetails{
 
624
                ControllerUUID:         fakeUUID,
 
625
                CACert:                 "certificate",
 
626
                UnresolvedAPIEndpoints: strUnsorted,
 
627
                APIEndpoints:           strResolved,
 
628
        }
 
629
        err := s.ControllerStore.UpdateController("controller-name", controllerDetails)
872
630
        c.Assert(err, jc.ErrorIsNil)
873
631
 
874
632
        addrs, hosts, changed := juju.PrepareEndpointsForCaching(
875
 
                info, [][]network.HostPort{unsortedHPs},
 
633
                controllerDetails, [][]network.HostPort{unsortedHPs},
876
634
        )
877
635
        c.Assert(addrs, gc.IsNil)
878
636
        c.Assert(hosts, gc.IsNil)
888
646
func (s *CacheAPIEndpointsSuite) TestResolveCalledWithInitialEndpoints(c *gc.C) {
889
647
        // Test that if no hostnames exist cached we call resolve (i.e.
890
648
        // simulate the behavior right after bootstrap)
891
 
        info := s.store.CreateInfo("controller-name")
 
649
 
892
650
        // Because Hostnames are sorted before caching, reordering them
893
651
        // will simulate they have changed.
894
652
        unsortedHPs := network.NewHostPorts(1234,
911
669
                "fc00::2", // from ipv6.example.com
912
670
        )
913
671
        strResolved := network.HostPortsToStrings(resolvedHPs)
914
 
        info.SetAPIEndpoint(configstore.APIEndpoint{})
915
 
        err := info.Write()
 
672
 
 
673
        controllerDetails := jujuclient.ControllerDetails{
 
674
                ControllerUUID: fakeUUID,
 
675
                CACert:         "certificate",
 
676
        }
 
677
        err := s.ControllerStore.UpdateController("controller-name", controllerDetails)
916
678
        c.Assert(err, jc.ErrorIsNil)
917
679
 
918
680
        addrs, hosts, changed := juju.PrepareEndpointsForCaching(
919
 
                info, [][]network.HostPort{unsortedHPs},
 
681
                controllerDetails, [][]network.HostPort{unsortedHPs},
920
682
        )
921
683
        c.Assert(addrs, jc.DeepEquals, strResolved)
922
684
        c.Assert(hosts, jc.DeepEquals, strSorted)
929
691
        c.Assert(c.GetTestLog(), jc.Contains, expectLog)
930
692
}
931
693
 
932
 
func (s *CacheAPIEndpointsSuite) assertEndpointsPreferIPv6False(c *gc.C, info configstore.EnvironInfo) {
 
694
func (s *CacheAPIEndpointsSuite) assertEndpoints(c *gc.C, controllerDetails *jujuclient.ControllerDetails) {
933
695
        c.Assert(s.resolveNumCalls, gc.Equals, 1)
934
696
        c.Assert(s.numResolved, gc.Equals, 10)
935
 
        endpoint := info.APIEndpoint()
936
697
        // Check Addresses after resolving.
937
 
        c.Check(endpoint.Addresses, jc.DeepEquals, []string{
 
698
        c.Check(controllerDetails.APIEndpoints, jc.DeepEquals, []string{
938
699
                s.apiHostPort.NetAddr(), // Last endpoint successfully connected to is always on top.
939
700
                "0.1.2.1:1234",          // From ipv4+4.example.com
940
701
                "0.1.2.2:1234",          // From ipv4+4.example.com
956
717
                "[fc00::9]:1234", // From ipv6+6.example.com
957
718
        })
958
719
        // Check Hostnames before resolving
959
 
        c.Check(endpoint.Hostnames, jc.DeepEquals, []string{
960
 
                s.apiHostPort.NetAddr(), // Last endpoint successfully connected to is always on top.
961
 
                "1.0.0.1:1234",
962
 
                "1.0.0.2:1235",
963
 
                "192.0.0.1:1234",
964
 
                "[2001:db8::1]:1234",
965
 
                "[2001:db8::2]:1235",
966
 
                "invalid host:1234",
967
 
                "ipv4+4.example.com:1234",
968
 
                "ipv4+6.example.com:1234",
969
 
                "ipv4.example.com:1234",
970
 
                "ipv6+4.example.com:1235",
971
 
                "ipv6+6.example.com:1234",
972
 
                "ipv6.example.com:1234",
973
 
                "localhost:1234",
974
 
                "localhost:1235",
975
 
                "[fc00::111]:1234",
976
 
        })
977
 
}
978
 
 
979
 
func (s *CacheAPIEndpointsSuite) assertEndpointsPreferIPv6True(c *gc.C, info configstore.EnvironInfo) {
980
 
        c.Assert(s.resolveNumCalls, gc.Equals, 1)
981
 
        c.Assert(s.numResolved, gc.Equals, 10)
982
 
        endpoint := info.APIEndpoint()
983
 
        // Check Addresses after resolving.
984
 
        c.Check(endpoint.Addresses, jc.DeepEquals, []string{
985
 
                s.apiHostPort.NetAddr(), // Last endpoint successfully connected to is always on top.
986
 
                "[2001:db8::1]:1234",
987
 
                "[2001:db8::2]:1235",
988
 
                "0.1.2.1:1234", // From ipv4+4.example.com
989
 
                "0.1.2.2:1234", // From ipv4+4.example.com
990
 
                "0.1.2.3:1234", // From ipv4+6.example.com
991
 
                "0.1.2.5:1234", // From ipv4.example.com
992
 
                "0.1.2.6:1234", // From ipv6+4.example.com
993
 
                "1.0.0.1:1234",
994
 
                "1.0.0.2:1235",
995
 
                "192.0.0.1:1234",
996
 
                "localhost:1234",  // Left intact on purpose.
997
 
                "localhost:1235",  // Left intact on purpose.
998
 
                "[fc00::10]:1234", // From ipv6.example.com
999
 
                "[fc00::111]:1234",
1000
 
                "[fc00::3]:1234", // From ipv4+6.example.com
1001
 
                "[fc00::6]:1234", // From ipv6+4.example.com
1002
 
                "[fc00::8]:1234", // From ipv6+6.example.com
1003
 
                "[fc00::9]:1234", // From ipv6+6.example.com
1004
 
        })
1005
 
        // Check Hostnames before resolving
1006
 
        c.Check(endpoint.Hostnames, jc.DeepEquals, []string{
1007
 
                s.apiHostPort.NetAddr(), // Last endpoint successfully connected to is always on top.
1008
 
                "[2001:db8::1]:1234",
1009
 
                "[2001:db8::2]:1235",
1010
 
                "1.0.0.1:1234",
1011
 
                "1.0.0.2:1235",
1012
 
                "192.0.0.1:1234",
 
720
        c.Check(controllerDetails.UnresolvedAPIEndpoints, jc.DeepEquals, []string{
 
721
                s.apiHostPort.NetAddr(), // Last endpoint successfully connected to is always on top.
 
722
                "1.0.0.1:1234",
 
723
                "1.0.0.2:1235",
 
724
                "192.0.0.1:1234",
 
725
                "[2001:db8::1]:1234",
 
726
                "[2001:db8::2]:1235",
1013
727
                "invalid host:1234",
1014
728
                "ipv4+4.example.com:1234",
1015
729
                "ipv4+6.example.com:1234",
1084
798
 
1085
799
var fakeUUID = "df136476-12e9-11e4-8a70-b2227cce2b54"
1086
800
 
1087
 
var dummyStoreInfo = &environInfo{
1088
 
        creds: configstore.APICredentials{
1089
 
                User:     "admin@local",
1090
 
                Password: "hunter2",
1091
 
        },
1092
 
        endpoint: configstore.APIEndpoint{
1093
 
                Addresses:  []string{"foo.invalid"},
1094
 
                CACert:     "certificated",
1095
 
                ModelUUID:  fakeUUID,
1096
 
                ServerUUID: fakeUUID,
1097
 
        },
 
801
func noBootstrapConfig(controllerName string) (*config.Config, error) {
 
802
        return nil, errors.NotFoundf("bootstrap config for controller %s", controllerName)
 
803
}
 
804
 
 
805
func newAPIConnectionFromNames(
 
806
        c *gc.C,
 
807
        controller, account, model string,
 
808
        store jujuclient.ClientStore,
 
809
        apiOpen api.OpenFunc,
 
810
        getBootstrapConfig func(string) (*config.Config, error),
 
811
) (api.Connection, error) {
 
812
        params := juju.NewAPIConnectionParams{
 
813
                Store:           store,
 
814
                ControllerName:  controller,
 
815
                BootstrapConfig: getBootstrapConfig,
 
816
                DialOpts:        api.DefaultDialOpts(),
 
817
        }
 
818
        if account != "" {
 
819
                accountDetails, err := store.AccountByName(controller, account)
 
820
                c.Assert(err, jc.ErrorIsNil)
 
821
                params.AccountDetails = accountDetails
 
822
        }
 
823
        if model != "" {
 
824
                modelDetails, err := store.ModelByName(controller, account, model)
 
825
                c.Assert(err, jc.ErrorIsNil)
 
826
                params.ModelUUID = modelDetails.ModelUUID
 
827
        }
 
828
        return juju.NewAPIFromStore(params, apiOpen)
1098
829
}