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