github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/apiserver/client/client_test.go (about) 1 // Copyright 2012-2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package client_test 5 6 import ( 7 "fmt" 8 "sort" 9 "strconv" 10 "strings" 11 "time" 12 13 "github.com/juju/errors" 14 "github.com/juju/names" 15 jc "github.com/juju/testing/checkers" 16 "github.com/juju/utils/series" 17 "github.com/juju/version" 18 gc "gopkg.in/check.v1" 19 "gopkg.in/juju/charm.v6-unstable" 20 21 "github.com/juju/juju/agent" 22 "github.com/juju/juju/api" 23 "github.com/juju/juju/apiserver/client" 24 "github.com/juju/juju/apiserver/common" 25 "github.com/juju/juju/apiserver/params" 26 "github.com/juju/juju/apiserver/testing" 27 "github.com/juju/juju/constraints" 28 "github.com/juju/juju/environs" 29 "github.com/juju/juju/environs/config" 30 "github.com/juju/juju/environs/manual" 31 toolstesting "github.com/juju/juju/environs/tools/testing" 32 "github.com/juju/juju/instance" 33 "github.com/juju/juju/network" 34 "github.com/juju/juju/rpc" 35 "github.com/juju/juju/state" 36 "github.com/juju/juju/state/multiwatcher" 37 "github.com/juju/juju/state/presence" 38 "github.com/juju/juju/status" 39 coretesting "github.com/juju/juju/testing" 40 "github.com/juju/juju/testing/factory" 41 jujuversion "github.com/juju/juju/version" 42 ) 43 44 type Killer interface { 45 Kill() error 46 } 47 48 type serverSuite struct { 49 baseSuite 50 client *client.Client 51 } 52 53 var _ = gc.Suite(&serverSuite{}) 54 55 func (s *serverSuite) SetUpTest(c *gc.C) { 56 s.baseSuite.SetUpTest(c) 57 58 var err error 59 auth := testing.FakeAuthorizer{ 60 Tag: s.AdminUserTag(c), 61 EnvironManager: true, 62 } 63 s.client, err = client.NewClient(s.State, common.NewResources(), auth) 64 c.Assert(err, jc.ErrorIsNil) 65 } 66 67 func (s *serverSuite) setAgentPresence(c *gc.C, machineId string) *presence.Pinger { 68 m, err := s.State.Machine(machineId) 69 c.Assert(err, jc.ErrorIsNil) 70 pinger, err := m.SetAgentPresence() 71 c.Assert(err, jc.ErrorIsNil) 72 s.State.StartSync() 73 err = m.WaitAgentPresence(coretesting.LongWait) 74 c.Assert(err, jc.ErrorIsNil) 75 return pinger 76 } 77 78 func (s *serverSuite) TestModelUsersInfo(c *gc.C) { 79 testAdmin := s.AdminUserTag(c) 80 owner, err := s.State.ModelUser(testAdmin) 81 c.Assert(err, jc.ErrorIsNil) 82 83 localUser1 := s.makeLocalModelUser(c, "ralphdoe", "Ralph Doe") 84 localUser2 := s.makeLocalModelUser(c, "samsmith", "Sam Smith") 85 remoteUser1 := s.Factory.MakeModelUser(c, &factory.ModelUserParams{User: "bobjohns@ubuntuone", DisplayName: "Bob Johns"}) 86 remoteUser2 := s.Factory.MakeModelUser(c, &factory.ModelUserParams{User: "nicshaw@idprovider", DisplayName: "Nic Shaw"}) 87 88 results, err := s.client.ModelUserInfo() 89 c.Assert(err, jc.ErrorIsNil) 90 var expected params.ModelUserInfoResults 91 for _, r := range []struct { 92 user *state.ModelUser 93 info *params.ModelUserInfo 94 }{ 95 { 96 owner, 97 ¶ms.ModelUserInfo{ 98 UserName: owner.UserName(), 99 DisplayName: owner.DisplayName(), 100 Access: "write", 101 }, 102 }, { 103 localUser1, 104 ¶ms.ModelUserInfo{ 105 UserName: "ralphdoe@local", 106 DisplayName: "Ralph Doe", 107 Access: "write", 108 }, 109 }, { 110 localUser2, 111 ¶ms.ModelUserInfo{ 112 UserName: "samsmith@local", 113 DisplayName: "Sam Smith", 114 Access: "write", 115 }, 116 }, { 117 remoteUser1, 118 ¶ms.ModelUserInfo{ 119 UserName: "bobjohns@ubuntuone", 120 DisplayName: "Bob Johns", 121 Access: "write", 122 }, 123 }, { 124 remoteUser2, 125 ¶ms.ModelUserInfo{ 126 UserName: "nicshaw@idprovider", 127 DisplayName: "Nic Shaw", 128 Access: "write", 129 }, 130 }, 131 } { 132 r.info.LastConnection = lastConnPointer(c, r.user) 133 expected.Results = append(expected.Results, params.ModelUserInfoResult{Result: r.info}) 134 } 135 136 sort.Sort(ByUserName(expected.Results)) 137 sort.Sort(ByUserName(results.Results)) 138 c.Assert(results, jc.DeepEquals, expected) 139 } 140 141 func lastConnPointer(c *gc.C, modelUser *state.ModelUser) *time.Time { 142 lastConn, err := modelUser.LastConnection() 143 if err != nil { 144 if state.IsNeverConnectedError(err) { 145 return nil 146 } 147 c.Fatal(err) 148 } 149 return &lastConn 150 } 151 152 // ByUserName implements sort.Interface for []params.ModelUserInfoResult based on 153 // the UserName field. 154 type ByUserName []params.ModelUserInfoResult 155 156 func (a ByUserName) Len() int { return len(a) } 157 func (a ByUserName) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 158 func (a ByUserName) Less(i, j int) bool { return a[i].Result.UserName < a[j].Result.UserName } 159 160 func (s *serverSuite) makeLocalModelUser(c *gc.C, username, displayname string) *state.ModelUser { 161 // factory.MakeUser will create an ModelUser for a local user by defalut 162 user := s.Factory.MakeUser(c, &factory.UserParams{Name: username, DisplayName: displayname}) 163 modelUser, err := s.State.ModelUser(user.UserTag()) 164 c.Assert(err, jc.ErrorIsNil) 165 return modelUser 166 } 167 168 func (s *serverSuite) TestSetEnvironAgentVersion(c *gc.C) { 169 args := params.SetModelAgentVersion{ 170 Version: version.MustParse("9.8.7"), 171 } 172 err := s.client.SetModelAgentVersion(args) 173 c.Assert(err, jc.ErrorIsNil) 174 175 envConfig, err := s.State.ModelConfig() 176 c.Assert(err, jc.ErrorIsNil) 177 agentVersion, found := envConfig.AllAttrs()["agent-version"] 178 c.Assert(found, jc.IsTrue) 179 c.Assert(agentVersion, gc.Equals, "9.8.7") 180 } 181 182 type mockEnviron struct { 183 environs.Environ 184 allInstancesCalled bool 185 err error 186 } 187 188 func (m *mockEnviron) AllInstances() ([]instance.Instance, error) { 189 m.allInstancesCalled = true 190 return nil, m.err 191 } 192 193 func (s *serverSuite) assertCheckProviderAPI(c *gc.C, envError error, expectErr string) { 194 env := &mockEnviron{err: envError} 195 s.PatchValue(client.GetEnvironment, func(cfg *config.Config) (environs.Environ, error) { 196 return env, nil 197 }) 198 args := params.SetModelAgentVersion{ 199 Version: version.MustParse("9.8.7"), 200 } 201 err := s.client.SetModelAgentVersion(args) 202 c.Assert(env.allInstancesCalled, jc.IsTrue) 203 if expectErr != "" { 204 c.Assert(err, gc.ErrorMatches, expectErr) 205 } else { 206 c.Assert(err, jc.ErrorIsNil) 207 } 208 } 209 210 func (s *serverSuite) TestCheckProviderAPISuccess(c *gc.C) { 211 s.assertCheckProviderAPI(c, nil, "") 212 s.assertCheckProviderAPI(c, environs.ErrPartialInstances, "") 213 s.assertCheckProviderAPI(c, environs.ErrNoInstances, "") 214 } 215 216 func (s *serverSuite) TestCheckProviderAPIFail(c *gc.C) { 217 s.assertCheckProviderAPI(c, fmt.Errorf("instances error"), "cannot make API call to provider: instances error") 218 } 219 220 func (s *serverSuite) assertSetEnvironAgentVersion(c *gc.C) { 221 args := params.SetModelAgentVersion{ 222 Version: version.MustParse("9.8.7"), 223 } 224 err := s.client.SetModelAgentVersion(args) 225 c.Assert(err, jc.ErrorIsNil) 226 envConfig, err := s.State.ModelConfig() 227 c.Assert(err, jc.ErrorIsNil) 228 agentVersion, found := envConfig.AllAttrs()["agent-version"] 229 c.Assert(found, jc.IsTrue) 230 c.Assert(agentVersion, gc.Equals, "9.8.7") 231 } 232 233 func (s *serverSuite) assertSetEnvironAgentVersionBlocked(c *gc.C, msg string) { 234 args := params.SetModelAgentVersion{ 235 Version: version.MustParse("9.8.7"), 236 } 237 err := s.client.SetModelAgentVersion(args) 238 s.AssertBlocked(c, err, msg) 239 } 240 241 func (s *serverSuite) TestBlockDestroySetEnvironAgentVersion(c *gc.C) { 242 s.BlockDestroyModel(c, "TestBlockDestroySetEnvironAgentVersion") 243 s.assertSetEnvironAgentVersion(c) 244 } 245 246 func (s *serverSuite) TestBlockRemoveSetEnvironAgentVersion(c *gc.C) { 247 s.BlockRemoveObject(c, "TestBlockRemoveSetEnvironAgentVersion") 248 s.assertSetEnvironAgentVersion(c) 249 } 250 251 func (s *serverSuite) TestBlockChangesSetEnvironAgentVersion(c *gc.C) { 252 s.BlockAllChanges(c, "TestBlockChangesSetEnvironAgentVersion") 253 s.assertSetEnvironAgentVersionBlocked(c, "TestBlockChangesSetEnvironAgentVersion") 254 } 255 256 func (s *serverSuite) TestAbortCurrentUpgrade(c *gc.C) { 257 // Create a provisioned controller. 258 machine, err := s.State.AddMachine("series", state.JobManageModel) 259 c.Assert(err, jc.ErrorIsNil) 260 err = machine.SetProvisioned(instance.Id("i-blah"), "fake-nonce", nil) 261 c.Assert(err, jc.ErrorIsNil) 262 263 // Start an upgrade. 264 _, err = s.State.EnsureUpgradeInfo( 265 machine.Id(), 266 version.MustParse("1.2.3"), 267 version.MustParse("9.8.7"), 268 ) 269 c.Assert(err, jc.ErrorIsNil) 270 isUpgrading, err := s.State.IsUpgrading() 271 c.Assert(err, jc.ErrorIsNil) 272 c.Assert(isUpgrading, jc.IsTrue) 273 274 // Abort it. 275 err = s.client.AbortCurrentUpgrade() 276 c.Assert(err, jc.ErrorIsNil) 277 278 isUpgrading, err = s.State.IsUpgrading() 279 c.Assert(err, jc.ErrorIsNil) 280 c.Assert(isUpgrading, jc.IsFalse) 281 } 282 283 func (s *serverSuite) assertAbortCurrentUpgradeBlocked(c *gc.C, msg string) { 284 err := s.client.AbortCurrentUpgrade() 285 s.AssertBlocked(c, err, msg) 286 } 287 288 func (s *serverSuite) assertAbortCurrentUpgrade(c *gc.C) { 289 err := s.client.AbortCurrentUpgrade() 290 c.Assert(err, jc.ErrorIsNil) 291 isUpgrading, err := s.State.IsUpgrading() 292 c.Assert(err, jc.ErrorIsNil) 293 c.Assert(isUpgrading, jc.IsFalse) 294 } 295 296 func (s *serverSuite) setupAbortCurrentUpgradeBlocked(c *gc.C) { 297 // Create a provisioned controller. 298 machine, err := s.State.AddMachine("series", state.JobManageModel) 299 c.Assert(err, jc.ErrorIsNil) 300 err = machine.SetProvisioned(instance.Id("i-blah"), "fake-nonce", nil) 301 c.Assert(err, jc.ErrorIsNil) 302 303 // Start an upgrade. 304 _, err = s.State.EnsureUpgradeInfo( 305 machine.Id(), 306 version.MustParse("1.2.3"), 307 version.MustParse("9.8.7"), 308 ) 309 c.Assert(err, jc.ErrorIsNil) 310 isUpgrading, err := s.State.IsUpgrading() 311 c.Assert(err, jc.ErrorIsNil) 312 c.Assert(isUpgrading, jc.IsTrue) 313 } 314 315 func (s *serverSuite) TestBlockDestroyAbortCurrentUpgrade(c *gc.C) { 316 s.setupAbortCurrentUpgradeBlocked(c) 317 s.BlockDestroyModel(c, "TestBlockDestroyAbortCurrentUpgrade") 318 s.assertAbortCurrentUpgrade(c) 319 } 320 321 func (s *serverSuite) TestBlockRemoveAbortCurrentUpgrade(c *gc.C) { 322 s.setupAbortCurrentUpgradeBlocked(c) 323 s.BlockRemoveObject(c, "TestBlockRemoveAbortCurrentUpgrade") 324 s.assertAbortCurrentUpgrade(c) 325 } 326 327 func (s *serverSuite) TestBlockChangesAbortCurrentUpgrade(c *gc.C) { 328 s.setupAbortCurrentUpgradeBlocked(c) 329 s.BlockAllChanges(c, "TestBlockChangesAbortCurrentUpgrade") 330 s.assertAbortCurrentUpgradeBlocked(c, "TestBlockChangesAbortCurrentUpgrade") 331 } 332 333 type clientSuite struct { 334 baseSuite 335 } 336 337 var _ = gc.Suite(&clientSuite{}) 338 339 // clearSinceTimes zeros out the updated timestamps inside status 340 // so we can easily check the results. 341 func clearSinceTimes(status *params.FullStatus) { 342 for serviceId, service := range status.Services { 343 for unitId, unit := range service.Units { 344 unit.WorkloadStatus.Since = nil 345 unit.AgentStatus.Since = nil 346 for id, subord := range unit.Subordinates { 347 subord.WorkloadStatus.Since = nil 348 subord.AgentStatus.Since = nil 349 unit.Subordinates[id] = subord 350 } 351 service.Units[unitId] = unit 352 } 353 service.Status.Since = nil 354 status.Services[serviceId] = service 355 } 356 for id, machine := range status.Machines { 357 machine.AgentStatus.Since = nil 358 machine.InstanceStatus.Since = nil 359 status.Machines[id] = machine 360 } 361 } 362 363 func (s *clientSuite) TestClientStatus(c *gc.C) { 364 s.setUpScenario(c) 365 status, err := s.APIState.Client().Status(nil) 366 clearSinceTimes(status) 367 c.Assert(err, jc.ErrorIsNil) 368 c.Assert(status, jc.DeepEquals, scenarioStatus) 369 } 370 371 func (s *clientSuite) TestClientCharmInfo(c *gc.C) { 372 var clientCharmInfoTests = []struct { 373 about string 374 charm string 375 url string 376 expectedActions *charm.Actions 377 err string 378 }{ 379 { 380 about: "dummy charm which contains an expectedActions spec", 381 charm: "dummy", 382 url: "local:quantal/dummy-1", 383 expectedActions: &charm.Actions{ 384 ActionSpecs: map[string]charm.ActionSpec{ 385 "snapshot": { 386 Description: "Take a snapshot of the database.", 387 Params: map[string]interface{}{ 388 "type": "object", 389 "title": "snapshot", 390 "description": "Take a snapshot of the database.", 391 "properties": map[string]interface{}{ 392 "outfile": map[string]interface{}{ 393 "default": "foo.bz2", 394 "description": "The file to write out to.", 395 "type": "string", 396 }, 397 }, 398 }, 399 }, 400 }, 401 }, 402 }, 403 { 404 about: "retrieves charm info", 405 // Use wordpress for tests so that we can compare Provides and Requires. 406 charm: "wordpress", 407 expectedActions: &charm.Actions{ActionSpecs: map[string]charm.ActionSpec{ 408 "fakeaction": { 409 Description: "No description", 410 Params: map[string]interface{}{ 411 "type": "object", 412 "title": "fakeaction", 413 "description": "No description", 414 "properties": map[string]interface{}{}, 415 }, 416 }, 417 }}, 418 url: "local:quantal/wordpress-3", 419 }, 420 { 421 about: "invalid URL", 422 charm: "wordpress", 423 url: "not-valid!", 424 err: `URL has invalid charm or bundle name: "not-valid!"`, 425 }, 426 { 427 about: "invalid schema", 428 charm: "wordpress", 429 url: "not-valid:your-arguments", 430 err: `charm or bundle URL has invalid schema: "not-valid:your-arguments"`, 431 }, 432 { 433 about: "unknown charm", 434 charm: "wordpress", 435 url: "cs:missing/one-1", 436 err: `charm "cs:missing/one-1" not found \(not found\)`, 437 }, 438 } 439 440 for i, t := range clientCharmInfoTests { 441 c.Logf("test %d. %s", i, t.about) 442 charm := s.AddTestingCharm(c, t.charm) 443 info, err := s.APIState.Client().CharmInfo(t.url) 444 if t.err != "" { 445 c.Check(err, gc.ErrorMatches, t.err) 446 continue 447 } 448 c.Assert(err, jc.ErrorIsNil) 449 expected := &api.CharmInfo{ 450 Revision: charm.Revision(), 451 URL: charm.URL().String(), 452 Config: charm.Config(), 453 Meta: charm.Meta(), 454 Actions: charm.Actions(), 455 } 456 c.Check(info, jc.DeepEquals, expected) 457 c.Check(info.Actions, jc.DeepEquals, t.expectedActions) 458 } 459 } 460 461 func (s *clientSuite) TestClientModelInfo(c *gc.C) { 462 conf, _ := s.State.ModelConfig() 463 info, err := s.APIState.Client().ModelInfo() 464 c.Assert(err, jc.ErrorIsNil) 465 env, err := s.State.Model() 466 c.Assert(err, jc.ErrorIsNil) 467 c.Assert(info.DefaultSeries, gc.Equals, config.PreferredSeries(conf)) 468 c.Assert(info.ProviderType, gc.Equals, conf.Type()) 469 c.Assert(info.Name, gc.Equals, conf.Name()) 470 c.Assert(info.UUID, gc.Equals, env.UUID()) 471 c.Assert(info.ControllerUUID, gc.Equals, env.ControllerUUID()) 472 } 473 474 func assertLife(c *gc.C, entity state.Living, life state.Life) { 475 err := entity.Refresh() 476 c.Assert(err, jc.ErrorIsNil) 477 c.Assert(entity.Life(), gc.Equals, life) 478 } 479 480 func assertRemoved(c *gc.C, entity state.Living) { 481 err := entity.Refresh() 482 c.Assert(err, jc.Satisfies, errors.IsNotFound) 483 } 484 485 func assertKill(c *gc.C, killer Killer) { 486 c.Assert(killer.Kill(), gc.IsNil) 487 } 488 489 func (s *clientSuite) setupDestroyMachinesTest(c *gc.C) (*state.Machine, *state.Machine, *state.Machine, *state.Unit) { 490 m0, err := s.State.AddMachine("quantal", state.JobManageModel) 491 c.Assert(err, jc.ErrorIsNil) 492 m1, err := s.State.AddMachine("quantal", state.JobHostUnits) 493 c.Assert(err, jc.ErrorIsNil) 494 m2, err := s.State.AddMachine("quantal", state.JobHostUnits) 495 c.Assert(err, jc.ErrorIsNil) 496 497 sch := s.AddTestingCharm(c, "wordpress") 498 wordpress := s.AddTestingService(c, "wordpress", sch) 499 u, err := wordpress.AddUnit() 500 c.Assert(err, jc.ErrorIsNil) 501 err = u.AssignToMachine(m1) 502 c.Assert(err, jc.ErrorIsNil) 503 504 return m0, m1, m2, u 505 } 506 507 func (s *clientSuite) TestDestroyMachines(c *gc.C) { 508 m0, m1, m2, u := s.setupDestroyMachinesTest(c) 509 s.assertDestroyMachineSuccess(c, u, m0, m1, m2) 510 } 511 512 func (s *clientSuite) TestForceDestroyMachines(c *gc.C) { 513 s.assertForceDestroyMachines(c) 514 } 515 516 func (s *clientSuite) testClientUnitResolved(c *gc.C, retry bool, expectedResolvedMode state.ResolvedMode) { 517 // Setup: 518 s.setUpScenario(c) 519 u, err := s.State.Unit("wordpress/0") 520 c.Assert(err, jc.ErrorIsNil) 521 err = u.SetAgentStatus(status.StatusError, "gaaah", nil) 522 c.Assert(err, jc.ErrorIsNil) 523 // Code under test: 524 err = s.APIState.Client().Resolved("wordpress/0", retry) 525 c.Assert(err, jc.ErrorIsNil) 526 // Freshen the unit's state. 527 err = u.Refresh() 528 c.Assert(err, jc.ErrorIsNil) 529 // And now the actual test assertions: we set the unit as resolved via 530 // the API so it should have a resolved mode set. 531 mode := u.Resolved() 532 c.Assert(mode, gc.Equals, expectedResolvedMode) 533 } 534 535 func (s *clientSuite) TestClientUnitResolved(c *gc.C) { 536 s.testClientUnitResolved(c, false, state.ResolvedNoHooks) 537 } 538 539 func (s *clientSuite) TestClientUnitResolvedRetry(c *gc.C) { 540 s.testClientUnitResolved(c, true, state.ResolvedRetryHooks) 541 } 542 543 func (s *clientSuite) setupResolved(c *gc.C) *state.Unit { 544 s.setUpScenario(c) 545 u, err := s.State.Unit("wordpress/0") 546 c.Assert(err, jc.ErrorIsNil) 547 err = u.SetAgentStatus(status.StatusError, "gaaah", nil) 548 c.Assert(err, jc.ErrorIsNil) 549 return u 550 } 551 552 func (s *clientSuite) assertResolved(c *gc.C, u *state.Unit) { 553 err := s.APIState.Client().Resolved("wordpress/0", true) 554 c.Assert(err, jc.ErrorIsNil) 555 // Freshen the unit's state. 556 err = u.Refresh() 557 c.Assert(err, jc.ErrorIsNil) 558 // And now the actual test assertions: we set the unit as resolved via 559 // the API so it should have a resolved mode set. 560 mode := u.Resolved() 561 c.Assert(mode, gc.Equals, state.ResolvedRetryHooks) 562 } 563 564 func (s *clientSuite) assertResolvedBlocked(c *gc.C, u *state.Unit, msg string) { 565 err := s.APIState.Client().Resolved("wordpress/0", true) 566 s.AssertBlocked(c, err, msg) 567 } 568 569 func (s *clientSuite) TestBlockDestroyUnitResolved(c *gc.C) { 570 u := s.setupResolved(c) 571 s.BlockDestroyModel(c, "TestBlockDestroyUnitResolved") 572 s.assertResolved(c, u) 573 } 574 575 func (s *clientSuite) TestBlockRemoveUnitResolved(c *gc.C) { 576 u := s.setupResolved(c) 577 s.BlockRemoveObject(c, "TestBlockRemoveUnitResolved") 578 s.assertResolved(c, u) 579 } 580 581 func (s *clientSuite) TestBlockChangeUnitResolved(c *gc.C) { 582 u := s.setupResolved(c) 583 s.BlockAllChanges(c, "TestBlockChangeUnitResolved") 584 s.assertResolvedBlocked(c, u, "TestBlockChangeUnitResolved") 585 } 586 587 type clientRepoSuite struct { 588 baseSuite 589 testing.CharmStoreSuite 590 } 591 592 var _ = gc.Suite(&clientRepoSuite{}) 593 594 func (s *clientRepoSuite) SetUpSuite(c *gc.C) { 595 s.CharmStoreSuite.SetUpSuite(c) 596 s.baseSuite.SetUpSuite(c) 597 598 } 599 600 func (s *clientRepoSuite) TearDownSuite(c *gc.C) { 601 s.CharmStoreSuite.TearDownSuite(c) 602 s.baseSuite.TearDownSuite(c) 603 } 604 605 func (s *clientRepoSuite) SetUpTest(c *gc.C) { 606 s.baseSuite.SetUpTest(c) 607 s.CharmStoreSuite.Session = s.baseSuite.Session 608 s.CharmStoreSuite.SetUpTest(c) 609 610 c.Assert(s.APIState, gc.NotNil) 611 } 612 613 func (s *clientRepoSuite) TearDownTest(c *gc.C) { 614 s.CharmStoreSuite.TearDownTest(c) 615 s.baseSuite.TearDownTest(c) 616 } 617 618 func (s *clientSuite) TestClientWatchAll(c *gc.C) { 619 // A very simple end-to-end test, because 620 // all the logic is tested elsewhere. 621 m, err := s.State.AddMachine("quantal", state.JobManageModel) 622 c.Assert(err, jc.ErrorIsNil) 623 err = m.SetProvisioned("i-0", agent.BootstrapNonce, nil) 624 c.Assert(err, jc.ErrorIsNil) 625 watcher, err := s.APIState.Client().WatchAll() 626 c.Assert(err, jc.ErrorIsNil) 627 defer func() { 628 err := watcher.Stop() 629 c.Assert(err, jc.ErrorIsNil) 630 }() 631 deltas, err := watcher.Next() 632 c.Assert(err, jc.ErrorIsNil) 633 c.Assert(len(deltas), gc.Equals, 1) 634 d0, ok := deltas[0].Entity.(*multiwatcher.MachineInfo) 635 c.Assert(ok, jc.IsTrue) 636 d0.JujuStatus.Since = nil 637 d0.MachineStatus.Since = nil 638 if !c.Check(deltas, gc.DeepEquals, []multiwatcher.Delta{{ 639 Entity: &multiwatcher.MachineInfo{ 640 ModelUUID: s.State.ModelUUID(), 641 Id: m.Id(), 642 InstanceId: "i-0", 643 JujuStatus: multiwatcher.StatusInfo{ 644 Current: status.StatusPending, 645 Data: map[string]interface{}{}, 646 }, 647 MachineStatus: multiwatcher.StatusInfo{ 648 Current: status.StatusPending, 649 Data: map[string]interface{}{}, 650 }, 651 Life: multiwatcher.Life("alive"), 652 Series: "quantal", 653 Jobs: []multiwatcher.MachineJob{state.JobManageModel.ToParams()}, 654 Addresses: []network.Address{}, 655 HardwareCharacteristics: &instance.HardwareCharacteristics{}, 656 HasVote: false, 657 WantsVote: true, 658 }, 659 }}) { 660 c.Logf("got:") 661 for _, d := range deltas { 662 c.Logf("%#v\n", d.Entity) 663 } 664 } 665 } 666 667 func (s *clientSuite) TestClientSetModelConstraints(c *gc.C) { 668 // Set constraints for the model. 669 cons, err := constraints.Parse("mem=4096", "cpu-cores=2") 670 c.Assert(err, jc.ErrorIsNil) 671 err = s.APIState.Client().SetModelConstraints(cons) 672 c.Assert(err, jc.ErrorIsNil) 673 674 // Ensure the constraints have been correctly updated. 675 obtained, err := s.State.ModelConstraints() 676 c.Assert(err, jc.ErrorIsNil) 677 c.Assert(obtained, gc.DeepEquals, cons) 678 } 679 680 func (s *clientSuite) assertSetModelConstraints(c *gc.C) { 681 // Set constraints for the model. 682 cons, err := constraints.Parse("mem=4096", "cpu-cores=2") 683 c.Assert(err, jc.ErrorIsNil) 684 err = s.APIState.Client().SetModelConstraints(cons) 685 c.Assert(err, jc.ErrorIsNil) 686 // Ensure the constraints have been correctly updated. 687 obtained, err := s.State.ModelConstraints() 688 c.Assert(err, jc.ErrorIsNil) 689 c.Assert(obtained, gc.DeepEquals, cons) 690 } 691 692 func (s *clientSuite) assertSetModelConstraintsBlocked(c *gc.C, msg string) { 693 // Set constraints for the model. 694 cons, err := constraints.Parse("mem=4096", "cpu-cores=2") 695 c.Assert(err, jc.ErrorIsNil) 696 err = s.APIState.Client().SetModelConstraints(cons) 697 s.AssertBlocked(c, err, msg) 698 } 699 700 func (s *clientSuite) TestBlockDestroyClientSetModelConstraints(c *gc.C) { 701 s.BlockDestroyModel(c, "TestBlockDestroyClientSetModelConstraints") 702 s.assertSetModelConstraints(c) 703 } 704 705 func (s *clientSuite) TestBlockRemoveClientSetModelConstraints(c *gc.C) { 706 s.BlockRemoveObject(c, "TestBlockRemoveClientSetModelConstraints") 707 s.assertSetModelConstraints(c) 708 } 709 710 func (s *clientSuite) TestBlockChangesClientSetModelConstraints(c *gc.C) { 711 s.BlockAllChanges(c, "TestBlockChangesClientSetModelConstraints") 712 s.assertSetModelConstraintsBlocked(c, "TestBlockChangesClientSetModelConstraints") 713 } 714 715 func (s *clientSuite) TestClientGetModelConstraints(c *gc.C) { 716 // Set constraints for the model. 717 cons, err := constraints.Parse("mem=4096", "cpu-cores=2") 718 c.Assert(err, jc.ErrorIsNil) 719 err = s.State.SetModelConstraints(cons) 720 c.Assert(err, jc.ErrorIsNil) 721 722 // Check we can get the constraints. 723 obtained, err := s.APIState.Client().GetModelConstraints() 724 c.Assert(err, jc.ErrorIsNil) 725 c.Assert(obtained, gc.DeepEquals, cons) 726 } 727 728 func (s *clientSuite) TestClientPublicAddressErrors(c *gc.C) { 729 s.setUpScenario(c) 730 _, err := s.APIState.Client().PublicAddress("wordpress") 731 c.Assert(err, gc.ErrorMatches, `unknown unit or machine "wordpress"`) 732 _, err = s.APIState.Client().PublicAddress("0") 733 c.Assert(err, gc.ErrorMatches, `error fetching address for machine "0": public no address`) 734 _, err = s.APIState.Client().PublicAddress("wordpress/0") 735 c.Assert(err, gc.ErrorMatches, `error fetching address for unit "wordpress/0": public no address`) 736 } 737 738 func (s *clientSuite) TestClientPublicAddressMachine(c *gc.C) { 739 s.setUpScenario(c) 740 network.SetPreferIPv6(false) 741 742 // Internally, network.SelectPublicAddress is used; the "most public" 743 // address is returned. 744 m1, err := s.State.Machine("1") 745 c.Assert(err, jc.ErrorIsNil) 746 cloudLocalAddress := network.NewScopedAddress("cloudlocal", network.ScopeCloudLocal) 747 publicAddress := network.NewScopedAddress("public", network.ScopePublic) 748 err = m1.SetProviderAddresses(cloudLocalAddress) 749 c.Assert(err, jc.ErrorIsNil) 750 addr, err := s.APIState.Client().PublicAddress("1") 751 c.Assert(err, jc.ErrorIsNil) 752 c.Assert(addr, gc.Equals, "cloudlocal") 753 err = m1.SetProviderAddresses(cloudLocalAddress, publicAddress) 754 addr, err = s.APIState.Client().PublicAddress("1") 755 c.Assert(err, jc.ErrorIsNil) 756 c.Assert(addr, gc.Equals, "public") 757 } 758 759 func (s *clientSuite) TestClientPublicAddressUnit(c *gc.C) { 760 s.setUpScenario(c) 761 762 m1, err := s.State.Machine("1") 763 publicAddress := network.NewScopedAddress("public", network.ScopePublic) 764 err = m1.SetProviderAddresses(publicAddress) 765 c.Assert(err, jc.ErrorIsNil) 766 addr, err := s.APIState.Client().PublicAddress("wordpress/0") 767 c.Assert(err, jc.ErrorIsNil) 768 c.Assert(addr, gc.Equals, "public") 769 } 770 771 func (s *clientSuite) TestClientPrivateAddressErrors(c *gc.C) { 772 s.setUpScenario(c) 773 _, err := s.APIState.Client().PrivateAddress("wordpress") 774 c.Assert(err, gc.ErrorMatches, `unknown unit or machine "wordpress"`) 775 _, err = s.APIState.Client().PrivateAddress("0") 776 c.Assert(err, gc.ErrorMatches, `error fetching address for machine "0": private no address`) 777 _, err = s.APIState.Client().PrivateAddress("wordpress/0") 778 c.Assert(err, gc.ErrorMatches, `error fetching address for unit "wordpress/0": private no address`) 779 } 780 781 func (s *clientSuite) TestClientPrivateAddress(c *gc.C) { 782 s.setUpScenario(c) 783 network.SetPreferIPv6(false) 784 785 // Internally, network.SelectInternalAddress is used; the public 786 // address if no cloud-local one is available. 787 m1, err := s.State.Machine("1") 788 c.Assert(err, jc.ErrorIsNil) 789 cloudLocalAddress := network.NewScopedAddress("cloudlocal", network.ScopeCloudLocal) 790 publicAddress := network.NewScopedAddress("public", network.ScopePublic) 791 err = m1.SetProviderAddresses(publicAddress) 792 c.Assert(err, jc.ErrorIsNil) 793 addr, err := s.APIState.Client().PrivateAddress("1") 794 c.Assert(err, jc.ErrorIsNil) 795 c.Assert(addr, gc.Equals, "public") 796 err = m1.SetProviderAddresses(cloudLocalAddress, publicAddress) 797 addr, err = s.APIState.Client().PrivateAddress("1") 798 c.Assert(err, jc.ErrorIsNil) 799 c.Assert(addr, gc.Equals, "cloudlocal") 800 } 801 802 func (s *clientSuite) TestClientPrivateAddressUnit(c *gc.C) { 803 s.setUpScenario(c) 804 805 m1, err := s.State.Machine("1") 806 privateAddress := network.NewScopedAddress("private", network.ScopeCloudLocal) 807 err = m1.SetProviderAddresses(privateAddress) 808 c.Assert(err, jc.ErrorIsNil) 809 addr, err := s.APIState.Client().PrivateAddress("wordpress/0") 810 c.Assert(err, jc.ErrorIsNil) 811 c.Assert(addr, gc.Equals, "private") 812 } 813 814 func (s *serverSuite) TestClientModelGet(c *gc.C) { 815 envConfig, err := s.State.ModelConfig() 816 c.Assert(err, jc.ErrorIsNil) 817 result, err := s.client.ModelGet() 818 c.Assert(err, jc.ErrorIsNil) 819 c.Assert(result.Config, gc.DeepEquals, envConfig.AllAttrs()) 820 } 821 822 func (s *serverSuite) assertEnvValue(c *gc.C, key string, expected interface{}) { 823 envConfig, err := s.State.ModelConfig() 824 c.Assert(err, jc.ErrorIsNil) 825 value, found := envConfig.AllAttrs()[key] 826 c.Assert(found, jc.IsTrue) 827 c.Assert(value, gc.Equals, expected) 828 } 829 830 func (s *serverSuite) assertEnvValueMissing(c *gc.C, key string) { 831 envConfig, err := s.State.ModelConfig() 832 c.Assert(err, jc.ErrorIsNil) 833 _, found := envConfig.AllAttrs()[key] 834 c.Assert(found, jc.IsFalse) 835 } 836 837 func (s *serverSuite) TestClientModelSet(c *gc.C) { 838 envConfig, err := s.State.ModelConfig() 839 c.Assert(err, jc.ErrorIsNil) 840 _, found := envConfig.AllAttrs()["some-key"] 841 c.Assert(found, jc.IsFalse) 842 843 params := params.ModelSet{ 844 Config: map[string]interface{}{ 845 "some-key": "value", 846 "other-key": "other value"}, 847 } 848 err = s.client.ModelSet(params) 849 c.Assert(err, jc.ErrorIsNil) 850 s.assertEnvValue(c, "some-key", "value") 851 s.assertEnvValue(c, "other-key", "other value") 852 } 853 854 func (s *serverSuite) TestClientModelSetImmutable(c *gc.C) { 855 // The various immutable config values are tested in 856 // environs/config/config_test.go, so just choosing one here. 857 params := params.ModelSet{ 858 Config: map[string]interface{}{"state-port": "1"}, 859 } 860 err := s.client.ModelSet(params) 861 c.Check(err, gc.ErrorMatches, `cannot change state-port from .* to 1`) 862 } 863 864 func (s *serverSuite) assertModelSetBlocked(c *gc.C, args map[string]interface{}, msg string) { 865 err := s.client.ModelSet(params.ModelSet{args}) 866 s.AssertBlocked(c, err, msg) 867 } 868 869 func (s *serverSuite) TestBlockChangesClientModelSet(c *gc.C) { 870 s.BlockAllChanges(c, "TestBlockChangesClientModelSet") 871 args := map[string]interface{}{"some-key": "value"} 872 s.assertModelSetBlocked(c, args, "TestBlockChangesClientModelSet") 873 } 874 875 func (s *serverSuite) TestClientModelSetDeprecated(c *gc.C) { 876 envConfig, err := s.State.ModelConfig() 877 c.Assert(err, jc.ErrorIsNil) 878 url := envConfig.AllAttrs()["agent-metadata-url"] 879 c.Assert(url, gc.Equals, "") 880 881 args := params.ModelSet{ 882 Config: map[string]interface{}{"tools-metadata-url": "value"}, 883 } 884 err = s.client.ModelSet(args) 885 c.Assert(err, jc.ErrorIsNil) 886 s.assertEnvValue(c, "agent-metadata-url", "value") 887 s.assertEnvValue(c, "tools-metadata-url", "value") 888 } 889 890 func (s *serverSuite) TestClientModelSetCannotChangeAgentVersion(c *gc.C) { 891 args := params.ModelSet{ 892 map[string]interface{}{"agent-version": "9.9.9"}, 893 } 894 err := s.client.ModelSet(args) 895 c.Assert(err, gc.ErrorMatches, "agent-version cannot be changed") 896 897 // It's okay to pass env back with the same agent-version. 898 result, err := s.client.ModelGet() 899 c.Assert(err, jc.ErrorIsNil) 900 c.Assert(result.Config["agent-version"], gc.NotNil) 901 args.Config["agent-version"] = result.Config["agent-version"] 902 err = s.client.ModelSet(args) 903 c.Assert(err, jc.ErrorIsNil) 904 } 905 906 func (s *serverSuite) TestClientModelUnset(c *gc.C) { 907 err := s.State.UpdateModelConfig(map[string]interface{}{"abc": 123}, nil, nil) 908 c.Assert(err, jc.ErrorIsNil) 909 910 args := params.ModelUnset{[]string{"abc"}} 911 err = s.client.ModelUnset(args) 912 c.Assert(err, jc.ErrorIsNil) 913 s.assertEnvValueMissing(c, "abc") 914 } 915 916 func (s *serverSuite) TestBlockClientModelUnset(c *gc.C) { 917 err := s.State.UpdateModelConfig(map[string]interface{}{"abc": 123}, nil, nil) 918 c.Assert(err, jc.ErrorIsNil) 919 s.BlockAllChanges(c, "TestBlockClientModelUnset") 920 921 args := params.ModelUnset{[]string{"abc"}} 922 err = s.client.ModelUnset(args) 923 s.AssertBlocked(c, err, "TestBlockClientModelUnset") 924 } 925 926 func (s *serverSuite) TestClientModelUnsetMissing(c *gc.C) { 927 // It's okay to unset a non-existent attribute. 928 args := params.ModelUnset{[]string{"not_there"}} 929 err := s.client.ModelUnset(args) 930 c.Assert(err, jc.ErrorIsNil) 931 } 932 933 func (s *serverSuite) TestClientModelUnsetError(c *gc.C) { 934 err := s.State.UpdateModelConfig(map[string]interface{}{"abc": 123}, nil, nil) 935 c.Assert(err, jc.ErrorIsNil) 936 937 // "type" may not be removed, and this will cause an error. 938 // If any one attribute's removal causes an error, there 939 // should be no change. 940 args := params.ModelUnset{[]string{"abc", "type"}} 941 err = s.client.ModelUnset(args) 942 c.Assert(err, gc.ErrorMatches, "type: expected string, got nothing") 943 s.assertEnvValue(c, "abc", 123) 944 } 945 946 func (s *clientSuite) TestClientFindTools(c *gc.C) { 947 result, err := s.APIState.Client().FindTools(99, -1, "", "") 948 c.Assert(err, jc.ErrorIsNil) 949 c.Assert(result.Error, jc.Satisfies, params.IsCodeNotFound) 950 toolstesting.UploadToStorage(c, s.DefaultToolsStorage, "released", version.MustParseBinary("2.99.0-precise-amd64")) 951 result, err = s.APIState.Client().FindTools(2, 99, "precise", "amd64") 952 c.Assert(err, jc.ErrorIsNil) 953 c.Assert(result.Error, gc.IsNil) 954 c.Assert(result.List, gc.HasLen, 1) 955 c.Assert(result.List[0].Version, gc.Equals, version.MustParseBinary("2.99.0-precise-amd64")) 956 url := fmt.Sprintf("https://%s/model/%s/tools/%s", 957 s.APIState.Addr(), coretesting.ModelTag.Id(), result.List[0].Version) 958 c.Assert(result.List[0].URL, gc.Equals, url) 959 } 960 961 func (s *clientSuite) checkMachine(c *gc.C, id, series, cons string) { 962 // Ensure the machine was actually created. 963 machine, err := s.BackingState.Machine(id) 964 c.Assert(err, jc.ErrorIsNil) 965 c.Assert(machine.Series(), gc.Equals, series) 966 c.Assert(machine.Jobs(), gc.DeepEquals, []state.MachineJob{state.JobHostUnits}) 967 machineConstraints, err := machine.Constraints() 968 c.Assert(err, jc.ErrorIsNil) 969 c.Assert(machineConstraints.String(), gc.Equals, cons) 970 } 971 972 func (s *clientSuite) TestClientAddMachinesDefaultSeries(c *gc.C) { 973 apiParams := make([]params.AddMachineParams, 3) 974 for i := 0; i < 3; i++ { 975 apiParams[i] = params.AddMachineParams{ 976 Jobs: []multiwatcher.MachineJob{multiwatcher.JobHostUnits}, 977 } 978 } 979 machines, err := s.APIState.Client().AddMachines(apiParams) 980 c.Assert(err, jc.ErrorIsNil) 981 c.Assert(len(machines), gc.Equals, 3) 982 for i, machineResult := range machines { 983 c.Assert(machineResult.Machine, gc.DeepEquals, strconv.Itoa(i)) 984 s.checkMachine(c, machineResult.Machine, series.LatestLts(), apiParams[i].Constraints.String()) 985 } 986 } 987 988 func (s *clientSuite) assertAddMachines(c *gc.C) { 989 apiParams := make([]params.AddMachineParams, 3) 990 for i := 0; i < 3; i++ { 991 apiParams[i] = params.AddMachineParams{ 992 Jobs: []multiwatcher.MachineJob{multiwatcher.JobHostUnits}, 993 } 994 } 995 machines, err := s.APIState.Client().AddMachines(apiParams) 996 c.Assert(err, jc.ErrorIsNil) 997 c.Assert(len(machines), gc.Equals, 3) 998 for i, machineResult := range machines { 999 c.Assert(machineResult.Machine, gc.DeepEquals, strconv.Itoa(i)) 1000 s.checkMachine(c, machineResult.Machine, series.LatestLts(), apiParams[i].Constraints.String()) 1001 } 1002 } 1003 1004 func (s *clientSuite) assertAddMachinesBlocked(c *gc.C, msg string) { 1005 apiParams := make([]params.AddMachineParams, 3) 1006 for i := 0; i < 3; i++ { 1007 apiParams[i] = params.AddMachineParams{ 1008 Jobs: []multiwatcher.MachineJob{multiwatcher.JobHostUnits}, 1009 } 1010 } 1011 _, err := s.APIState.Client().AddMachines(apiParams) 1012 s.AssertBlocked(c, err, msg) 1013 } 1014 1015 func (s *clientSuite) TestBlockDestroyClientAddMachinesDefaultSeries(c *gc.C) { 1016 s.BlockDestroyModel(c, "TestBlockDestroyClientAddMachinesDefaultSeries") 1017 s.assertAddMachines(c) 1018 } 1019 1020 func (s *clientSuite) TestBlockRemoveClientAddMachinesDefaultSeries(c *gc.C) { 1021 s.BlockRemoveObject(c, "TestBlockRemoveClientAddMachinesDefaultSeries") 1022 s.assertAddMachines(c) 1023 } 1024 1025 func (s *clientSuite) TestBlockChangesClientAddMachines(c *gc.C) { 1026 s.BlockAllChanges(c, "TestBlockChangesClientAddMachines") 1027 s.assertAddMachinesBlocked(c, "TestBlockChangesClientAddMachines") 1028 } 1029 1030 func (s *clientSuite) TestClientAddMachinesWithSeries(c *gc.C) { 1031 apiParams := make([]params.AddMachineParams, 3) 1032 for i := 0; i < 3; i++ { 1033 apiParams[i] = params.AddMachineParams{ 1034 Series: "quantal", 1035 Jobs: []multiwatcher.MachineJob{multiwatcher.JobHostUnits}, 1036 } 1037 } 1038 machines, err := s.APIState.Client().AddMachines(apiParams) 1039 c.Assert(err, jc.ErrorIsNil) 1040 c.Assert(len(machines), gc.Equals, 3) 1041 for i, machineResult := range machines { 1042 c.Assert(machineResult.Machine, gc.DeepEquals, strconv.Itoa(i)) 1043 s.checkMachine(c, machineResult.Machine, "quantal", apiParams[i].Constraints.String()) 1044 } 1045 } 1046 1047 func (s *clientSuite) TestClientAddMachineInsideMachine(c *gc.C) { 1048 _, err := s.State.AddMachine("quantal", state.JobHostUnits) 1049 c.Assert(err, jc.ErrorIsNil) 1050 1051 machines, err := s.APIState.Client().AddMachines([]params.AddMachineParams{{ 1052 Jobs: []multiwatcher.MachineJob{multiwatcher.JobHostUnits}, 1053 ContainerType: instance.LXC, 1054 ParentId: "0", 1055 Series: "quantal", 1056 }}) 1057 c.Assert(err, jc.ErrorIsNil) 1058 c.Assert(machines, gc.HasLen, 1) 1059 c.Assert(machines[0].Machine, gc.Equals, "0/lxc/0") 1060 } 1061 1062 // updateConfig sets config variable with given key to a given value 1063 // Asserts that no errors were encountered. 1064 func (s *baseSuite) updateConfig(c *gc.C, key string, block bool) { 1065 err := s.State.UpdateModelConfig(map[string]interface{}{key: block}, nil, nil) 1066 c.Assert(err, jc.ErrorIsNil) 1067 } 1068 1069 func (s *clientSuite) TestClientAddMachinesWithConstraints(c *gc.C) { 1070 apiParams := make([]params.AddMachineParams, 3) 1071 for i := 0; i < 3; i++ { 1072 apiParams[i] = params.AddMachineParams{ 1073 Jobs: []multiwatcher.MachineJob{multiwatcher.JobHostUnits}, 1074 } 1075 } 1076 // The last machine has some constraints. 1077 apiParams[2].Constraints = constraints.MustParse("mem=4G") 1078 machines, err := s.APIState.Client().AddMachines(apiParams) 1079 c.Assert(err, jc.ErrorIsNil) 1080 c.Assert(len(machines), gc.Equals, 3) 1081 for i, machineResult := range machines { 1082 c.Assert(machineResult.Machine, gc.DeepEquals, strconv.Itoa(i)) 1083 s.checkMachine(c, machineResult.Machine, series.LatestLts(), apiParams[i].Constraints.String()) 1084 } 1085 } 1086 1087 func (s *clientSuite) TestClientAddMachinesWithPlacement(c *gc.C) { 1088 apiParams := make([]params.AddMachineParams, 4) 1089 for i := range apiParams { 1090 apiParams[i] = params.AddMachineParams{ 1091 Jobs: []multiwatcher.MachineJob{multiwatcher.JobHostUnits}, 1092 } 1093 } 1094 apiParams[0].Placement = instance.MustParsePlacement("lxc") 1095 apiParams[1].Placement = instance.MustParsePlacement("lxc:0") 1096 apiParams[1].ContainerType = instance.LXC 1097 apiParams[2].Placement = instance.MustParsePlacement("admin:invalid") 1098 apiParams[3].Placement = instance.MustParsePlacement("admin:valid") 1099 machines, err := s.APIState.Client().AddMachines(apiParams) 1100 c.Assert(err, jc.ErrorIsNil) 1101 c.Assert(len(machines), gc.Equals, 4) 1102 c.Assert(machines[0].Machine, gc.Equals, "0/lxc/0") 1103 c.Assert(machines[1].Error, gc.ErrorMatches, "container type and placement are mutually exclusive") 1104 c.Assert(machines[2].Error, gc.ErrorMatches, "cannot add a new machine: invalid placement is invalid") 1105 c.Assert(machines[3].Machine, gc.Equals, "1") 1106 1107 m, err := s.BackingState.Machine(machines[3].Machine) 1108 c.Assert(err, jc.ErrorIsNil) 1109 c.Assert(m.Placement(), gc.DeepEquals, apiParams[3].Placement.Directive) 1110 } 1111 1112 func (s *clientSuite) TestClientAddMachinesSomeErrors(c *gc.C) { 1113 // Here we check that adding a number of containers correctly handles the 1114 // case that some adds succeed and others fail and report the errors 1115 // accordingly. 1116 // We will set up params to the AddMachines API to attempt to create 3 machines. 1117 // Machines 0 and 1 will be added successfully. 1118 // Remaining machines will fail due to different reasons. 1119 1120 // Create a machine to host the requested containers. 1121 host, err := s.State.AddMachine("quantal", state.JobHostUnits) 1122 c.Assert(err, jc.ErrorIsNil) 1123 // The host only supports lxc containers. 1124 err = host.SetSupportedContainers([]instance.ContainerType{instance.LXC}) 1125 c.Assert(err, jc.ErrorIsNil) 1126 1127 // Set up params for adding 3 containers. 1128 apiParams := make([]params.AddMachineParams, 3) 1129 for i := range apiParams { 1130 apiParams[i] = params.AddMachineParams{ 1131 Jobs: []multiwatcher.MachineJob{multiwatcher.JobHostUnits}, 1132 } 1133 } 1134 // This will cause a add-machine to fail due to an unsupported container. 1135 apiParams[2].ContainerType = instance.KVM 1136 apiParams[2].ParentId = host.Id() 1137 machines, err := s.APIState.Client().AddMachines(apiParams) 1138 c.Assert(err, jc.ErrorIsNil) 1139 c.Assert(len(machines), gc.Equals, 3) 1140 1141 // Check the results - machines 2 and 3 will have errors. 1142 c.Check(machines[0].Machine, gc.Equals, "1") 1143 c.Check(machines[0].Error, gc.IsNil) 1144 c.Check(machines[1].Machine, gc.Equals, "2") 1145 c.Check(machines[1].Error, gc.IsNil) 1146 c.Check(machines[2].Error, gc.ErrorMatches, "cannot add a new machine: machine 0 cannot host kvm containers") 1147 } 1148 1149 func (s *clientSuite) TestClientAddMachinesWithInstanceIdSomeErrors(c *gc.C) { 1150 apiParams := make([]params.AddMachineParams, 3) 1151 addrs := network.NewAddresses("1.2.3.4") 1152 hc := instance.MustParseHardware("mem=4G") 1153 for i := 0; i < 3; i++ { 1154 apiParams[i] = params.AddMachineParams{ 1155 Jobs: []multiwatcher.MachineJob{multiwatcher.JobHostUnits}, 1156 InstanceId: instance.Id(fmt.Sprintf("1234-%d", i)), 1157 Nonce: "foo", 1158 HardwareCharacteristics: hc, 1159 Addrs: params.FromNetworkAddresses(addrs), 1160 } 1161 } 1162 // This will cause the last add-machine to fail. 1163 apiParams[2].Nonce = "" 1164 machines, err := s.APIState.Client().AddMachines(apiParams) 1165 c.Assert(err, jc.ErrorIsNil) 1166 c.Assert(len(machines), gc.Equals, 3) 1167 for i, machineResult := range machines { 1168 if i == 2 { 1169 c.Assert(machineResult.Error, gc.NotNil) 1170 c.Assert(machineResult.Error, gc.ErrorMatches, "cannot add a new machine: cannot add a machine with an instance id and no nonce") 1171 } else { 1172 c.Assert(machineResult.Machine, gc.DeepEquals, strconv.Itoa(i)) 1173 s.checkMachine(c, machineResult.Machine, series.LatestLts(), apiParams[i].Constraints.String()) 1174 instanceId := fmt.Sprintf("1234-%d", i) 1175 s.checkInstance(c, machineResult.Machine, instanceId, "foo", hc, addrs) 1176 } 1177 } 1178 } 1179 1180 func (s *clientSuite) checkInstance(c *gc.C, id, instanceId, nonce string, 1181 hc instance.HardwareCharacteristics, addr []network.Address) { 1182 1183 machine, err := s.BackingState.Machine(id) 1184 c.Assert(err, jc.ErrorIsNil) 1185 machineInstanceId, err := machine.InstanceId() 1186 c.Assert(err, jc.ErrorIsNil) 1187 c.Assert(machine.CheckProvisioned(nonce), jc.IsTrue) 1188 c.Assert(machineInstanceId, gc.Equals, instance.Id(instanceId)) 1189 machineHardware, err := machine.HardwareCharacteristics() 1190 c.Assert(err, jc.ErrorIsNil) 1191 c.Assert(machineHardware.String(), gc.Equals, hc.String()) 1192 c.Assert(machine.Addresses(), gc.DeepEquals, addr) 1193 } 1194 1195 func (s *clientSuite) TestInjectMachinesStillExists(c *gc.C) { 1196 results := new(params.AddMachinesResults) 1197 // We need to use Call directly because the client interface 1198 // no longer refers to InjectMachine. 1199 args := params.AddMachines{ 1200 MachineParams: []params.AddMachineParams{{ 1201 Jobs: []multiwatcher.MachineJob{multiwatcher.JobHostUnits}, 1202 InstanceId: "i-foo", 1203 Nonce: "nonce", 1204 }}, 1205 } 1206 err := s.APIState.APICall("Client", 1, "", "AddMachines", args, &results) 1207 c.Assert(err, jc.ErrorIsNil) 1208 c.Assert(results.Machines, gc.HasLen, 1) 1209 } 1210 1211 func (s *clientSuite) TestProvisioningScript(c *gc.C) { 1212 // Inject a machine and then call the ProvisioningScript API. 1213 // The result should be the same as when calling MachineConfig, 1214 // converting it to a cloudinit.MachineConfig, and disabling 1215 // apt_upgrade. 1216 apiParams := params.AddMachineParams{ 1217 Jobs: []multiwatcher.MachineJob{multiwatcher.JobHostUnits}, 1218 InstanceId: instance.Id("1234"), 1219 Nonce: "foo", 1220 HardwareCharacteristics: instance.MustParseHardware("arch=amd64"), 1221 } 1222 machines, err := s.APIState.Client().AddMachines([]params.AddMachineParams{apiParams}) 1223 c.Assert(err, jc.ErrorIsNil) 1224 c.Assert(len(machines), gc.Equals, 1) 1225 machineId := machines[0].Machine 1226 // Call ProvisioningScript. Normally ProvisioningScript and 1227 // MachineConfig are mutually exclusive; both of them will 1228 // allocate a api password for the machine agent. 1229 script, err := s.APIState.Client().ProvisioningScript(params.ProvisioningScriptParams{ 1230 MachineId: machineId, 1231 Nonce: apiParams.Nonce, 1232 }) 1233 c.Assert(err, jc.ErrorIsNil) 1234 icfg, err := client.InstanceConfig(s.State, machineId, apiParams.Nonce, "") 1235 c.Assert(err, jc.ErrorIsNil) 1236 provisioningScript, err := manual.ProvisioningScript(icfg) 1237 c.Assert(err, jc.ErrorIsNil) 1238 // ProvisioningScript internally calls MachineConfig, 1239 // which allocates a new, random password. Everything 1240 // about the scripts should be the same other than 1241 // the line containing "oldpassword" from agent.conf. 1242 scriptLines := strings.Split(script, "\n") 1243 provisioningScriptLines := strings.Split(provisioningScript, "\n") 1244 c.Assert(scriptLines, gc.HasLen, len(provisioningScriptLines)) 1245 for i, line := range scriptLines { 1246 if strings.Contains(line, "oldpassword") { 1247 continue 1248 } 1249 c.Assert(line, gc.Equals, provisioningScriptLines[i]) 1250 } 1251 } 1252 1253 func (s *clientSuite) TestProvisioningScriptDisablePackageCommands(c *gc.C) { 1254 apiParams := params.AddMachineParams{ 1255 Jobs: []multiwatcher.MachineJob{multiwatcher.JobHostUnits}, 1256 InstanceId: instance.Id("1234"), 1257 Nonce: "foo", 1258 HardwareCharacteristics: instance.MustParseHardware("arch=amd64"), 1259 } 1260 machines, err := s.APIState.Client().AddMachines([]params.AddMachineParams{apiParams}) 1261 c.Assert(err, jc.ErrorIsNil) 1262 c.Assert(len(machines), gc.Equals, 1) 1263 machineId := machines[0].Machine 1264 1265 provParams := params.ProvisioningScriptParams{ 1266 MachineId: machineId, 1267 Nonce: apiParams.Nonce, 1268 } 1269 1270 setUpdateBehavior := func(update, upgrade bool) { 1271 s.State.UpdateModelConfig( 1272 map[string]interface{}{ 1273 "enable-os-upgrade": upgrade, 1274 "enable-os-refresh-update": update, 1275 }, 1276 nil, 1277 nil, 1278 ) 1279 } 1280 1281 // Test enabling package commands 1282 provParams.DisablePackageCommands = false 1283 setUpdateBehavior(true, true) 1284 script, err := s.APIState.Client().ProvisioningScript(provParams) 1285 c.Assert(err, jc.ErrorIsNil) 1286 c.Check(script, jc.Contains, "apt-get update") 1287 c.Check(script, jc.Contains, "apt-get upgrade") 1288 1289 // Test disabling package commands 1290 provParams.DisablePackageCommands = true 1291 setUpdateBehavior(false, false) 1292 script, err = s.APIState.Client().ProvisioningScript(provParams) 1293 c.Assert(err, jc.ErrorIsNil) 1294 c.Check(script, gc.Not(jc.Contains), "apt-get update") 1295 c.Check(script, gc.Not(jc.Contains), "apt-get upgrade") 1296 1297 // Test client-specified DisablePackageCommands trumps environment 1298 // config variables. 1299 provParams.DisablePackageCommands = true 1300 setUpdateBehavior(true, true) 1301 script, err = s.APIState.Client().ProvisioningScript(provParams) 1302 c.Assert(err, jc.ErrorIsNil) 1303 c.Check(script, gc.Not(jc.Contains), "apt-get update") 1304 c.Check(script, gc.Not(jc.Contains), "apt-get upgrade") 1305 1306 // Test that in the abasence of a client-specified 1307 // DisablePackageCommands we use what's set in environment config. 1308 provParams.DisablePackageCommands = false 1309 setUpdateBehavior(false, false) 1310 //provParams.UpdateBehavior = ¶ms.UpdateBehavior{false, false} 1311 script, err = s.APIState.Client().ProvisioningScript(provParams) 1312 c.Assert(err, jc.ErrorIsNil) 1313 c.Check(script, gc.Not(jc.Contains), "apt-get update") 1314 c.Check(script, gc.Not(jc.Contains), "apt-get upgrade") 1315 } 1316 1317 var resolveCharmTests = []struct { 1318 about string 1319 url string 1320 resolved string 1321 parseErr string 1322 resolveErr string 1323 }{{ 1324 about: "wordpress resolved", 1325 url: "cs:wordpress", 1326 resolved: "cs:trusty/wordpress", 1327 }, { 1328 about: "mysql resolved", 1329 url: "cs:mysql", 1330 resolved: "cs:precise/mysql", 1331 }, { 1332 about: "riak resolved", 1333 url: "cs:riak", 1334 resolved: "cs:trusty/riak", 1335 }, { 1336 about: "fully qualified char reference", 1337 url: "cs:utopic/riak-5", 1338 resolved: "cs:utopic/riak-5", 1339 }, { 1340 about: "charm with series and no revision", 1341 url: "cs:precise/wordpress", 1342 resolved: "cs:precise/wordpress", 1343 }, { 1344 about: "fully qualified reference not found", 1345 url: "cs:utopic/riak-42", 1346 resolveErr: `cannot resolve URL "cs:utopic/riak-42": charm not found`, 1347 }, { 1348 about: "reference not found", 1349 url: "cs:no-such", 1350 resolveErr: `cannot resolve URL "cs:no-such": charm or bundle not found`, 1351 }, { 1352 about: "invalid charm name", 1353 url: "cs:", 1354 parseErr: `URL has invalid charm or bundle name: "cs:"`, 1355 }, { 1356 about: "local charm", 1357 url: "local:wordpress", 1358 resolveErr: `only charm store charm references are supported, with cs: schema`, 1359 }} 1360 1361 func (s *clientRepoSuite) TestResolveCharm(c *gc.C) { 1362 // Add some charms to be resolved later. 1363 for _, url := range []string{ 1364 "precise/wordpress-1", 1365 "trusty/wordpress-2", 1366 "precise/mysql-3", 1367 "trusty/riak-4", 1368 "utopic/riak-5", 1369 } { 1370 s.UploadCharm(c, url, "wordpress") 1371 } 1372 1373 // Run the tests. 1374 for i, test := range resolveCharmTests { 1375 c.Logf("test %d: %s", i, test.about) 1376 1377 client := s.APIState.Client() 1378 ref, err := charm.ParseURL(test.url) 1379 if test.parseErr == "" { 1380 if !c.Check(err, jc.ErrorIsNil) { 1381 continue 1382 } 1383 } else { 1384 c.Assert(err, gc.NotNil) 1385 c.Check(err, gc.ErrorMatches, test.parseErr) 1386 continue 1387 } 1388 1389 curl, err := client.ResolveCharm(ref) 1390 if test.resolveErr == "" { 1391 c.Assert(err, jc.ErrorIsNil) 1392 c.Check(curl.String(), gc.Equals, test.resolved) 1393 continue 1394 } 1395 c.Check(err, gc.ErrorMatches, test.resolveErr) 1396 c.Check(curl, gc.IsNil) 1397 } 1398 } 1399 1400 func (s *clientSuite) TestRetryProvisioning(c *gc.C) { 1401 machine, err := s.State.AddMachine("quantal", state.JobHostUnits) 1402 c.Assert(err, jc.ErrorIsNil) 1403 err = machine.SetStatus(status.StatusError, "error", nil) 1404 c.Assert(err, jc.ErrorIsNil) 1405 _, err = s.APIState.Client().RetryProvisioning(machine.Tag().(names.MachineTag)) 1406 c.Assert(err, jc.ErrorIsNil) 1407 1408 statusInfo, err := machine.Status() 1409 c.Assert(err, jc.ErrorIsNil) 1410 c.Assert(statusInfo.Status, gc.Equals, status.StatusError) 1411 c.Assert(statusInfo.Message, gc.Equals, "error") 1412 c.Assert(statusInfo.Data["transient"], jc.IsTrue) 1413 } 1414 1415 func (s *clientSuite) setupRetryProvisioning(c *gc.C) *state.Machine { 1416 machine, err := s.State.AddMachine("quantal", state.JobHostUnits) 1417 c.Assert(err, jc.ErrorIsNil) 1418 err = machine.SetStatus(status.StatusError, "error", nil) 1419 c.Assert(err, jc.ErrorIsNil) 1420 return machine 1421 } 1422 1423 func (s *clientSuite) assertRetryProvisioning(c *gc.C, machine *state.Machine) { 1424 _, err := s.APIState.Client().RetryProvisioning(machine.Tag().(names.MachineTag)) 1425 c.Assert(err, jc.ErrorIsNil) 1426 statusInfo, err := machine.Status() 1427 c.Assert(err, jc.ErrorIsNil) 1428 c.Assert(statusInfo.Status, gc.Equals, status.StatusError) 1429 c.Assert(statusInfo.Message, gc.Equals, "error") 1430 c.Assert(statusInfo.Data["transient"], jc.IsTrue) 1431 } 1432 1433 func (s *clientSuite) assertRetryProvisioningBlocked(c *gc.C, machine *state.Machine, msg string) { 1434 _, err := s.APIState.Client().RetryProvisioning(machine.Tag().(names.MachineTag)) 1435 s.AssertBlocked(c, err, msg) 1436 } 1437 1438 func (s *clientSuite) TestBlockDestroyRetryProvisioning(c *gc.C) { 1439 m := s.setupRetryProvisioning(c) 1440 s.BlockDestroyModel(c, "TestBlockDestroyRetryProvisioning") 1441 s.assertRetryProvisioning(c, m) 1442 } 1443 1444 func (s *clientSuite) TestBlockRemoveRetryProvisioning(c *gc.C) { 1445 m := s.setupRetryProvisioning(c) 1446 s.BlockRemoveObject(c, "TestBlockRemoveRetryProvisioning") 1447 s.assertRetryProvisioning(c, m) 1448 } 1449 1450 func (s *clientSuite) TestBlockChangesRetryProvisioning(c *gc.C) { 1451 m := s.setupRetryProvisioning(c) 1452 s.BlockAllChanges(c, "TestBlockChangesRetryProvisioning") 1453 s.assertRetryProvisioningBlocked(c, m, "TestBlockChangesRetryProvisioning") 1454 } 1455 1456 func (s *clientSuite) TestAPIHostPorts(c *gc.C) { 1457 server1Addresses := []network.Address{{ 1458 Value: "server-1", 1459 Type: network.HostName, 1460 Scope: network.ScopePublic, 1461 }, { 1462 Value: "10.0.0.1", 1463 Type: network.IPv4Address, 1464 Scope: network.ScopeCloudLocal, 1465 }} 1466 server2Addresses := []network.Address{{ 1467 Value: "::1", 1468 Type: network.IPv6Address, 1469 Scope: network.ScopeMachineLocal, 1470 }} 1471 stateAPIHostPorts := [][]network.HostPort{ 1472 network.AddressesWithPort(server1Addresses, 123), 1473 network.AddressesWithPort(server2Addresses, 456), 1474 } 1475 1476 err := s.State.SetAPIHostPorts(stateAPIHostPorts) 1477 c.Assert(err, jc.ErrorIsNil) 1478 apiHostPorts, err := s.APIState.Client().APIHostPorts() 1479 c.Assert(err, jc.ErrorIsNil) 1480 c.Assert(apiHostPorts, gc.DeepEquals, stateAPIHostPorts) 1481 } 1482 1483 func (s *clientSuite) TestClientAgentVersion(c *gc.C) { 1484 current := version.MustParse("1.2.0") 1485 s.PatchValue(&jujuversion.Current, current) 1486 result, err := s.APIState.Client().AgentVersion() 1487 c.Assert(err, jc.ErrorIsNil) 1488 c.Assert(result, gc.Equals, current) 1489 } 1490 1491 func (s *clientSuite) assertDestroyMachineSuccess(c *gc.C, u *state.Unit, m0, m1, m2 *state.Machine) { 1492 err := s.APIState.Client().DestroyMachines("0", "1", "2") 1493 c.Assert(err, gc.ErrorMatches, `some machines were not destroyed: machine 0 is required by the model; machine 1 has unit "wordpress/0" assigned`) 1494 assertLife(c, m0, state.Alive) 1495 assertLife(c, m1, state.Alive) 1496 assertLife(c, m2, state.Dying) 1497 1498 err = u.UnassignFromMachine() 1499 c.Assert(err, jc.ErrorIsNil) 1500 err = s.APIState.Client().DestroyMachines("0", "1", "2") 1501 c.Assert(err, gc.ErrorMatches, `some machines were not destroyed: machine 0 is required by the model`) 1502 assertLife(c, m0, state.Alive) 1503 assertLife(c, m1, state.Dying) 1504 assertLife(c, m2, state.Dying) 1505 } 1506 1507 func (s *clientSuite) assertBlockedErrorAndLiveliness( 1508 c *gc.C, 1509 err error, 1510 msg string, 1511 living1 state.Living, 1512 living2 state.Living, 1513 living3 state.Living, 1514 living4 state.Living, 1515 ) { 1516 s.AssertBlocked(c, err, msg) 1517 assertLife(c, living1, state.Alive) 1518 assertLife(c, living2, state.Alive) 1519 assertLife(c, living3, state.Alive) 1520 assertLife(c, living4, state.Alive) 1521 } 1522 1523 func (s *clientSuite) AssertBlocked(c *gc.C, err error, msg string) { 1524 c.Assert(params.IsCodeOperationBlocked(err), jc.IsTrue, gc.Commentf("error: %#v", err)) 1525 c.Assert(errors.Cause(err), gc.DeepEquals, &rpc.RequestError{ 1526 Message: msg, 1527 Code: "operation is blocked", 1528 }) 1529 } 1530 1531 func (s *clientSuite) TestBlockRemoveDestroyMachines(c *gc.C) { 1532 m0, m1, m2, u := s.setupDestroyMachinesTest(c) 1533 s.BlockRemoveObject(c, "TestBlockRemoveDestroyMachines") 1534 err := s.APIState.Client().DestroyMachines("0", "1", "2") 1535 s.assertBlockedErrorAndLiveliness(c, err, "TestBlockRemoveDestroyMachines", m0, m1, m2, u) 1536 } 1537 1538 func (s *clientSuite) TestBlockChangesDestroyMachines(c *gc.C) { 1539 m0, m1, m2, u := s.setupDestroyMachinesTest(c) 1540 s.BlockAllChanges(c, "TestBlockChangesDestroyMachines") 1541 err := s.APIState.Client().DestroyMachines("0", "1", "2") 1542 s.assertBlockedErrorAndLiveliness(c, err, "TestBlockChangesDestroyMachines", m0, m1, m2, u) 1543 } 1544 1545 func (s *clientSuite) TestBlockDestoryDestroyMachines(c *gc.C) { 1546 m0, m1, m2, u := s.setupDestroyMachinesTest(c) 1547 s.BlockDestroyModel(c, "TestBlockDestoryDestroyMachines") 1548 s.assertDestroyMachineSuccess(c, u, m0, m1, m2) 1549 } 1550 1551 func (s *clientSuite) TestAnyBlockForceDestroyMachines(c *gc.C) { 1552 // force bypasses all blocks 1553 s.BlockAllChanges(c, "TestAnyBlockForceDestroyMachines") 1554 s.BlockDestroyModel(c, "TestAnyBlockForceDestroyMachines") 1555 s.BlockRemoveObject(c, "TestAnyBlockForceDestroyMachines") 1556 s.assertForceDestroyMachines(c) 1557 } 1558 1559 func (s *clientSuite) assertForceDestroyMachines(c *gc.C) { 1560 m0, m1, m2, u := s.setupDestroyMachinesTest(c) 1561 1562 err := s.APIState.Client().ForceDestroyMachines("0", "1", "2") 1563 c.Assert(err, gc.ErrorMatches, `some machines were not destroyed: machine is required by the model`) 1564 assertLife(c, m0, state.Alive) 1565 assertLife(c, m1, state.Alive) 1566 assertLife(c, m2, state.Alive) 1567 assertLife(c, u, state.Alive) 1568 1569 err = s.State.Cleanup() 1570 c.Assert(err, jc.ErrorIsNil) 1571 assertLife(c, m0, state.Alive) 1572 assertLife(c, m1, state.Dead) 1573 assertLife(c, m2, state.Dead) 1574 assertRemoved(c, u) 1575 } 1576 1577 func (s *clientSuite) TestDestroyModel(c *gc.C) { 1578 // The full tests for DestroyModel are in modelmanager. 1579 // Here we just test that things are hooked up such that we can destroy 1580 // the model through the client endpoint to support older juju clients. 1581 err := s.APIState.Client().DestroyModel() 1582 c.Assert(err, jc.ErrorIsNil) 1583 1584 env, err := s.State.Model() 1585 c.Assert(err, jc.ErrorIsNil) 1586 c.Assert(env.Life(), gc.Equals, state.Dying) 1587 }