github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/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 "io" 9 "regexp" 10 "strconv" 11 "strings" 12 "sync" 13 14 "github.com/juju/errors" 15 "github.com/juju/names" 16 jc "github.com/juju/testing/checkers" 17 "github.com/juju/utils" 18 "github.com/juju/utils/featureflag" 19 gc "gopkg.in/check.v1" 20 "gopkg.in/juju/charm.v4" 21 charmtesting "gopkg.in/juju/charm.v4/testing" 22 "gopkg.in/mgo.v2" 23 24 "github.com/juju/juju/agent" 25 "github.com/juju/juju/api" 26 "github.com/juju/juju/apiserver/client" 27 "github.com/juju/juju/apiserver/common" 28 "github.com/juju/juju/apiserver/params" 29 "github.com/juju/juju/apiserver/testing" 30 "github.com/juju/juju/constraints" 31 "github.com/juju/juju/environs/config" 32 "github.com/juju/juju/environs/manual" 33 toolstesting "github.com/juju/juju/environs/tools/testing" 34 "github.com/juju/juju/instance" 35 "github.com/juju/juju/juju/osenv" 36 "github.com/juju/juju/network" 37 "github.com/juju/juju/provider/dummy" 38 "github.com/juju/juju/state" 39 "github.com/juju/juju/state/multiwatcher" 40 "github.com/juju/juju/state/presence" 41 statestorage "github.com/juju/juju/state/storage" 42 "github.com/juju/juju/storage" 43 "github.com/juju/juju/testcharms" 44 coretesting "github.com/juju/juju/testing" 45 "github.com/juju/juju/testing/factory" 46 "github.com/juju/juju/version" 47 ) 48 49 type clientSuite struct { 50 baseSuite 51 } 52 53 type Killer interface { 54 Kill() error 55 } 56 57 type serverSuite struct { 58 baseSuite 59 client *client.Client 60 } 61 62 var _ = gc.Suite(&serverSuite{}) 63 64 func (s *serverSuite) SetUpTest(c *gc.C) { 65 s.baseSuite.SetUpTest(c) 66 67 var err error 68 auth := testing.FakeAuthorizer{ 69 Tag: s.AdminUserTag(c), 70 EnvironManager: true, 71 } 72 s.client, err = client.NewClient(s.State, common.NewResources(), auth) 73 c.Assert(err, jc.ErrorIsNil) 74 } 75 76 func (s *serverSuite) setAgentPresence(c *gc.C, machineId string) *presence.Pinger { 77 m, err := s.State.Machine(machineId) 78 c.Assert(err, jc.ErrorIsNil) 79 pinger, err := m.SetAgentPresence() 80 c.Assert(err, jc.ErrorIsNil) 81 s.State.StartSync() 82 err = m.WaitAgentPresence(coretesting.LongWait) 83 c.Assert(err, jc.ErrorIsNil) 84 return pinger 85 } 86 87 func (s *serverSuite) TestEnsureAvailabilityDeprecated(c *gc.C) { 88 _, err := s.State.AddMachine("quantal", state.JobManageEnviron) 89 c.Assert(err, jc.ErrorIsNil) 90 // We have to ensure the agents are alive, or EnsureAvailability will 91 // create more to replace them. 92 pingerA := s.setAgentPresence(c, "0") 93 defer assertKill(c, pingerA) 94 95 machines, err := s.State.AllMachines() 96 c.Assert(err, jc.ErrorIsNil) 97 c.Assert(machines, gc.HasLen, 1) 98 c.Assert(machines[0].Series(), gc.Equals, "quantal") 99 100 arg := params.StateServersSpecs{[]params.StateServersSpec{{NumStateServers: 3}}} 101 results, err := s.client.EnsureAvailability(arg) 102 c.Assert(err, jc.ErrorIsNil) 103 c.Assert(results.Results, gc.HasLen, 1) 104 result := results.Results[0] 105 c.Assert(result.Error, gc.IsNil) 106 ensureAvailabilityResult := result.Result 107 c.Assert(ensureAvailabilityResult.Maintained, gc.DeepEquals, []string{"machine-0"}) 108 c.Assert(ensureAvailabilityResult.Added, gc.DeepEquals, []string{"machine-1", "machine-2"}) 109 c.Assert(ensureAvailabilityResult.Removed, gc.HasLen, 0) 110 111 machines, err = s.State.AllMachines() 112 c.Assert(err, jc.ErrorIsNil) 113 c.Assert(machines, gc.HasLen, 3) 114 c.Assert(machines[0].Series(), gc.Equals, "quantal") 115 c.Assert(machines[1].Series(), gc.Equals, "quantal") 116 c.Assert(machines[2].Series(), gc.Equals, "quantal") 117 } 118 119 func (s *serverSuite) TestBlockEnsureAvailabilityDeprecated(c *gc.C) { 120 _, err := s.State.AddMachine("quantal", state.JobManageEnviron) 121 c.Assert(err, jc.ErrorIsNil) 122 123 s.blockAllChanges(c) 124 125 arg := params.StateServersSpecs{[]params.StateServersSpec{{NumStateServers: 3}}} 126 results, err := s.client.EnsureAvailability(arg) 127 c.Assert(errors.Cause(err), gc.DeepEquals, common.ErrOperationBlocked) 128 c.Assert(results.Results, gc.HasLen, 0) 129 130 machines, err := s.State.AllMachines() //there 131 c.Assert(err, jc.ErrorIsNil) 132 c.Assert(machines, gc.HasLen, 1) 133 } 134 135 func (s *serverSuite) TestShareEnvironmentAddMissingLocalFails(c *gc.C) { 136 args := params.ModifyEnvironUsers{ 137 Changes: []params.ModifyEnvironUser{{ 138 UserTag: names.NewLocalUserTag("foobar").String(), 139 Action: params.AddEnvUser, 140 }}} 141 142 result, err := s.client.ShareEnvironment(args) 143 c.Assert(err, jc.ErrorIsNil) 144 expectedErr := `could not share environment: user "foobar" does not exist locally: user "foobar" not found` 145 c.Assert(result.OneError(), gc.ErrorMatches, expectedErr) 146 c.Assert(result.Results, gc.HasLen, 1) 147 c.Assert(result.Results[0].Error, gc.ErrorMatches, expectedErr) 148 } 149 150 func (s *serverSuite) TestUnshareEnvironment(c *gc.C) { 151 user := s.Factory.MakeEnvUser(c, nil) 152 _, err := s.State.EnvironmentUser(user.UserTag()) 153 c.Assert(err, jc.ErrorIsNil) 154 155 args := params.ModifyEnvironUsers{ 156 Changes: []params.ModifyEnvironUser{{ 157 UserTag: user.UserTag().String(), 158 Action: params.RemoveEnvUser, 159 }}} 160 161 result, err := s.client.ShareEnvironment(args) 162 c.Assert(err, jc.ErrorIsNil) 163 c.Assert(result.OneError(), gc.IsNil) 164 c.Assert(result.Results, gc.HasLen, 1) 165 c.Assert(result.Results[0].Error, gc.IsNil) 166 167 _, err = s.State.EnvironmentUser(user.UserTag()) 168 c.Assert(errors.IsNotFound(err), jc.IsTrue) 169 } 170 171 func (s *serverSuite) TestShareEnvironmentAddLocalUser(c *gc.C) { 172 user := s.Factory.MakeUser(c, &factory.UserParams{Name: "foobar", NoEnvUser: true}) 173 args := params.ModifyEnvironUsers{ 174 Changes: []params.ModifyEnvironUser{{ 175 UserTag: user.Tag().String(), 176 Action: params.AddEnvUser, 177 }}} 178 179 result, err := s.client.ShareEnvironment(args) 180 c.Assert(err, jc.ErrorIsNil) 181 c.Assert(result.OneError(), gc.IsNil) 182 c.Assert(result.Results, gc.HasLen, 1) 183 c.Assert(result.Results[0].Error, gc.IsNil) 184 185 envUser, err := s.State.EnvironmentUser(user.UserTag()) 186 c.Assert(err, jc.ErrorIsNil) 187 c.Assert(envUser.UserName(), gc.Equals, user.UserTag().Username()) 188 c.Assert(envUser.CreatedBy(), gc.Equals, dummy.AdminUserTag().Username()) 189 c.Assert(envUser.LastConnection(), gc.IsNil) 190 } 191 192 func (s *serverSuite) TestShareEnvironmentAddRemoteUser(c *gc.C) { 193 user := names.NewUserTag("foobar@ubuntuone") 194 args := params.ModifyEnvironUsers{ 195 Changes: []params.ModifyEnvironUser{{ 196 UserTag: user.String(), 197 Action: params.AddEnvUser, 198 }}} 199 200 result, err := s.client.ShareEnvironment(args) 201 c.Assert(err, jc.ErrorIsNil) 202 c.Assert(result.OneError(), gc.IsNil) 203 c.Assert(result.Results, gc.HasLen, 1) 204 c.Assert(result.Results[0].Error, gc.IsNil) 205 206 envUser, err := s.State.EnvironmentUser(user) 207 c.Assert(err, jc.ErrorIsNil) 208 c.Assert(envUser.UserName(), gc.Equals, user.Username()) 209 c.Assert(envUser.CreatedBy(), gc.Equals, dummy.AdminUserTag().Username()) 210 c.Assert(envUser.LastConnection(), gc.IsNil) 211 } 212 213 func (s *serverSuite) TestShareEnvironmentInvalidTags(c *gc.C) { 214 for _, testParam := range []struct { 215 tag string 216 validTag bool 217 }{{ 218 tag: "unit-foo/0", 219 validTag: true, 220 }, { 221 tag: "service-foo", 222 validTag: true, 223 }, { 224 tag: "relation-wordpress:db mysql:db", 225 validTag: true, 226 }, { 227 tag: "machine-0", 228 validTag: true, 229 }, { 230 tag: "user@local", 231 validTag: false, 232 }, { 233 tag: "user-Mua^h^h^h^arh", 234 validTag: true, 235 }, { 236 tag: "user@", 237 validTag: false, 238 }, { 239 tag: "user@ubuntuone", 240 validTag: false, 241 }, { 242 tag: "user@ubuntuone", 243 validTag: false, 244 }, { 245 tag: "@ubuntuone", 246 validTag: false, 247 }, { 248 tag: "in^valid.", 249 validTag: false, 250 }, { 251 tag: "", 252 validTag: false, 253 }, 254 } { 255 var expectedErr string 256 errPart := `could not share environment: "` + regexp.QuoteMeta(testParam.tag) + `" is not a valid ` 257 258 if testParam.validTag { 259 260 // The string is a valid tag, but not a user tag. 261 expectedErr = errPart + `user tag` 262 } else { 263 264 // The string is not a valid tag of any kind. 265 expectedErr = errPart + `tag` 266 } 267 268 args := params.ModifyEnvironUsers{ 269 Changes: []params.ModifyEnvironUser{{ 270 UserTag: testParam.tag, 271 Action: params.AddEnvUser, 272 }}} 273 274 _, err := s.client.ShareEnvironment(args) 275 result, err := s.client.ShareEnvironment(args) 276 c.Assert(err, jc.ErrorIsNil) 277 c.Assert(result.OneError(), gc.ErrorMatches, expectedErr) 278 c.Assert(result.Results, gc.HasLen, 1) 279 c.Assert(result.Results[0].Error, gc.ErrorMatches, expectedErr) 280 } 281 } 282 283 func (s *serverSuite) TestShareEnvironmentZeroArgs(c *gc.C) { 284 args := params.ModifyEnvironUsers{Changes: []params.ModifyEnvironUser{{}}} 285 286 _, err := s.client.ShareEnvironment(args) 287 result, err := s.client.ShareEnvironment(args) 288 c.Assert(err, jc.ErrorIsNil) 289 expectedErr := `could not share environment: "" is not a valid tag` 290 c.Assert(result.OneError(), gc.ErrorMatches, expectedErr) 291 c.Assert(result.Results, gc.HasLen, 1) 292 c.Assert(result.Results[0].Error, gc.ErrorMatches, expectedErr) 293 } 294 295 func (s *serverSuite) TestShareEnvironmentInvalidAction(c *gc.C) { 296 var dance params.EnvironAction = "dance" 297 args := params.ModifyEnvironUsers{ 298 Changes: []params.ModifyEnvironUser{{ 299 UserTag: "user-user@local", 300 Action: dance, 301 }}} 302 303 _, err := s.client.ShareEnvironment(args) 304 result, err := s.client.ShareEnvironment(args) 305 c.Assert(err, jc.ErrorIsNil) 306 expectedErr := `unknown action "dance"` 307 c.Assert(result.OneError(), gc.ErrorMatches, expectedErr) 308 c.Assert(result.Results, gc.HasLen, 1) 309 c.Assert(result.Results[0].Error, gc.ErrorMatches, expectedErr) 310 } 311 312 func (s *serverSuite) TestSetEnvironAgentVersion(c *gc.C) { 313 args := params.SetEnvironAgentVersion{ 314 Version: version.MustParse("9.8.7"), 315 } 316 err := s.client.SetEnvironAgentVersion(args) 317 c.Assert(err, jc.ErrorIsNil) 318 319 envConfig, err := s.State.EnvironConfig() 320 c.Assert(err, jc.ErrorIsNil) 321 agentVersion, found := envConfig.AllAttrs()["agent-version"] 322 c.Assert(found, jc.IsTrue) 323 c.Assert(agentVersion, gc.Equals, "9.8.7") 324 } 325 326 func (s *serverSuite) assertSetEnvironAgentVersionBlocked(c *gc.C, blocked bool) { 327 args := params.SetEnvironAgentVersion{ 328 Version: version.MustParse("9.8.7"), 329 } 330 err := s.client.SetEnvironAgentVersion(args) 331 if blocked { 332 c.Assert(errors.Cause(err), gc.Equals, common.ErrOperationBlocked) 333 } else { 334 c.Assert(err, jc.ErrorIsNil) 335 envConfig, err := s.State.EnvironConfig() 336 c.Assert(err, jc.ErrorIsNil) 337 agentVersion, found := envConfig.AllAttrs()["agent-version"] 338 c.Assert(found, jc.IsTrue) 339 c.Assert(agentVersion, gc.Equals, "9.8.7") 340 } 341 } 342 343 func (s *serverSuite) TestBlockDestroySetEnvironAgentVersion(c *gc.C) { 344 s.blockDestroyEnvironment(c) 345 s.assertSetEnvironAgentVersionBlocked(c, false) 346 } 347 348 func (s *serverSuite) TestBlockRemoveSetEnvironAgentVersion(c *gc.C) { 349 s.blockRemoveObject(c) 350 s.assertSetEnvironAgentVersionBlocked(c, false) 351 } 352 353 func (s *serverSuite) TestBlockChangesSetEnvironAgentVersion(c *gc.C) { 354 s.blockAllChanges(c) 355 s.assertSetEnvironAgentVersionBlocked(c, true) 356 } 357 358 func (s *serverSuite) TestAbortCurrentUpgrade(c *gc.C) { 359 // Create a provisioned state server. 360 machine, err := s.State.AddMachine("series", state.JobManageEnviron) 361 c.Assert(err, jc.ErrorIsNil) 362 err = machine.SetProvisioned(instance.Id("i-blah"), "fake-nonce", nil) 363 c.Assert(err, jc.ErrorIsNil) 364 365 // Start an upgrade. 366 _, err = s.State.EnsureUpgradeInfo( 367 machine.Id(), 368 version.MustParse("1.2.3"), 369 version.MustParse("9.8.7"), 370 ) 371 c.Assert(err, jc.ErrorIsNil) 372 isUpgrading, err := s.State.IsUpgrading() 373 c.Assert(err, jc.ErrorIsNil) 374 c.Assert(isUpgrading, jc.IsTrue) 375 376 // Abort it. 377 err = s.client.AbortCurrentUpgrade() 378 c.Assert(err, jc.ErrorIsNil) 379 380 isUpgrading, err = s.State.IsUpgrading() 381 c.Assert(err, jc.ErrorIsNil) 382 c.Assert(isUpgrading, jc.IsFalse) 383 } 384 385 func (s *serverSuite) assertAbortCurrentUpgradeBlocked(c *gc.C, blocked bool) { 386 err := s.client.AbortCurrentUpgrade() 387 388 if blocked { 389 c.Assert(errors.Cause(err), gc.Equals, common.ErrOperationBlocked) 390 } else { 391 c.Assert(err, jc.ErrorIsNil) 392 isUpgrading, err := s.State.IsUpgrading() 393 c.Assert(err, jc.ErrorIsNil) 394 c.Assert(isUpgrading, jc.IsFalse) 395 } 396 } 397 398 func (s *serverSuite) setupAbortCurrentUpgradeBlocked(c *gc.C) { 399 // Create a provisioned state server. 400 machine, err := s.State.AddMachine("series", state.JobManageEnviron) 401 c.Assert(err, jc.ErrorIsNil) 402 err = machine.SetProvisioned(instance.Id("i-blah"), "fake-nonce", nil) 403 c.Assert(err, jc.ErrorIsNil) 404 405 // Start an upgrade. 406 _, err = s.State.EnsureUpgradeInfo( 407 machine.Id(), 408 version.MustParse("1.2.3"), 409 version.MustParse("9.8.7"), 410 ) 411 c.Assert(err, jc.ErrorIsNil) 412 isUpgrading, err := s.State.IsUpgrading() 413 c.Assert(err, jc.ErrorIsNil) 414 c.Assert(isUpgrading, jc.IsTrue) 415 } 416 417 func (s *serverSuite) TestBlockDestroyAbortCurrentUpgrade(c *gc.C) { 418 s.setupAbortCurrentUpgradeBlocked(c) 419 s.blockDestroyEnvironment(c) 420 s.assertAbortCurrentUpgradeBlocked(c, false) 421 } 422 423 func (s *serverSuite) TestBlockRemoveAbortCurrentUpgrade(c *gc.C) { 424 s.setupAbortCurrentUpgradeBlocked(c) 425 s.blockRemoveObject(c) 426 s.assertAbortCurrentUpgradeBlocked(c, false) 427 } 428 429 func (s *serverSuite) TestBlockChangesAbortCurrentUpgrade(c *gc.C) { 430 s.setupAbortCurrentUpgradeBlocked(c) 431 s.blockAllChanges(c) 432 s.assertAbortCurrentUpgradeBlocked(c, true) 433 } 434 435 var _ = gc.Suite(&clientSuite{}) 436 437 func (s *clientSuite) TestClientStatus(c *gc.C) { 438 s.setUpScenario(c) 439 status, err := s.APIState.Client().Status(nil) 440 c.Assert(err, jc.ErrorIsNil) 441 c.Assert(status, jc.DeepEquals, scenarioStatus) 442 } 443 444 func (s *clientSuite) TestCompatibleSettingsParsing(c *gc.C) { 445 // Test the exported settings parsing in a compatible way. 446 s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 447 service, err := s.State.Service("dummy") 448 c.Assert(err, jc.ErrorIsNil) 449 ch, _, err := service.Charm() 450 c.Assert(err, jc.ErrorIsNil) 451 c.Assert(ch.URL().String(), gc.Equals, "local:quantal/dummy-1") 452 453 // Empty string will be returned as nil. 454 options := map[string]string{ 455 "title": "foobar", 456 "username": "", 457 } 458 settings, err := client.ParseSettingsCompatible(ch, options) 459 c.Assert(err, jc.ErrorIsNil) 460 c.Assert(settings, gc.DeepEquals, charm.Settings{ 461 "title": "foobar", 462 "username": nil, 463 }) 464 465 // Illegal settings lead to an error. 466 options = map[string]string{ 467 "yummy": "didgeridoo", 468 } 469 settings, err = client.ParseSettingsCompatible(ch, options) 470 c.Assert(err, gc.ErrorMatches, `unknown option "yummy"`) 471 } 472 473 var ( 474 validSetTestValue = "a value with spaces\nand newline\nand UTF-8 characters: \U0001F604 / \U0001F44D" 475 invalidSetTestValue = "a value with an invalid UTF-8 sequence: " + string([]byte{0xFF, 0xFF}) 476 correctedSetTestValue = "a value with an invalid UTF-8 sequence: \ufffd\ufffd" 477 ) 478 479 func (s *clientSuite) TestClientServiceSet(c *gc.C) { 480 dummy := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 481 482 err := s.APIState.Client().ServiceSet("dummy", map[string]string{ 483 "title": "foobar", 484 "username": validSetTestValue, 485 }) 486 c.Assert(err, jc.ErrorIsNil) 487 settings, err := dummy.ConfigSettings() 488 c.Assert(err, jc.ErrorIsNil) 489 c.Assert(settings, gc.DeepEquals, charm.Settings{ 490 "title": "foobar", 491 "username": validSetTestValue, 492 }) 493 494 // Test doesn't fail because Go JSON marshalling converts invalid 495 // UTF-8 sequences transparently to U+FFFD. The test demonstrates 496 // this behavior. It's a currently accepted behavior as it never has 497 // been a real-life issue. 498 err = s.APIState.Client().ServiceSet("dummy", map[string]string{ 499 "title": "foobar", 500 "username": invalidSetTestValue, 501 }) 502 c.Assert(err, jc.ErrorIsNil) 503 settings, err = dummy.ConfigSettings() 504 c.Assert(err, jc.ErrorIsNil) 505 c.Assert(settings, gc.DeepEquals, charm.Settings{ 506 "title": "foobar", 507 "username": correctedSetTestValue, 508 }) 509 510 err = s.APIState.Client().ServiceSet("dummy", map[string]string{ 511 "title": "barfoo", 512 "username": "", 513 }) 514 c.Assert(err, jc.ErrorIsNil) 515 settings, err = dummy.ConfigSettings() 516 c.Assert(err, jc.ErrorIsNil) 517 c.Assert(settings, gc.DeepEquals, charm.Settings{ 518 "title": "barfoo", 519 "username": "", 520 }) 521 } 522 523 func (s *serverSuite) assertServiceSetBlocked(c *gc.C, blocked bool, dummy *state.Service) { 524 err := s.client.ServiceSet(params.ServiceSet{ 525 ServiceName: "dummy", 526 Options: map[string]string{ 527 "title": "foobar", 528 "username": validSetTestValue}}) 529 if blocked { 530 c.Assert(errors.Cause(err), gc.DeepEquals, common.ErrOperationBlocked) 531 } else { 532 c.Assert(err, jc.ErrorIsNil) 533 settings, err := dummy.ConfigSettings() 534 c.Assert(err, jc.ErrorIsNil) 535 c.Assert(settings, gc.DeepEquals, charm.Settings{ 536 "title": "foobar", 537 "username": validSetTestValue, 538 }) 539 } 540 } 541 func (s *serverSuite) TestBlockDestroyServiceSet(c *gc.C) { 542 dummy := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 543 s.blockDestroyEnvironment(c) 544 s.assertServiceSetBlocked(c, false, dummy) 545 } 546 547 func (s *serverSuite) TestBlockRemoveServiceSet(c *gc.C) { 548 dummy := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 549 s.blockRemoveObject(c) 550 s.assertServiceSetBlocked(c, false, dummy) 551 } 552 553 func (s *serverSuite) TestBlockChangesServiceSet(c *gc.C) { 554 dummy := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 555 s.blockAllChanges(c) 556 s.assertServiceSetBlocked(c, true, dummy) 557 } 558 559 func (s *clientSuite) TestClientServerUnset(c *gc.C) { 560 dummy := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 561 562 err := s.APIState.Client().ServiceSet("dummy", map[string]string{ 563 "title": "foobar", 564 "username": "user name", 565 }) 566 c.Assert(err, jc.ErrorIsNil) 567 settings, err := dummy.ConfigSettings() 568 c.Assert(err, jc.ErrorIsNil) 569 c.Assert(settings, gc.DeepEquals, charm.Settings{ 570 "title": "foobar", 571 "username": "user name", 572 }) 573 574 err = s.APIState.Client().ServiceUnset("dummy", []string{"username"}) 575 c.Assert(err, jc.ErrorIsNil) 576 settings, err = dummy.ConfigSettings() 577 c.Assert(err, jc.ErrorIsNil) 578 c.Assert(settings, gc.DeepEquals, charm.Settings{ 579 "title": "foobar", 580 }) 581 } 582 583 func (s *serverSuite) setupServerUnsetBlocked(c *gc.C) *state.Service { 584 dummy := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 585 586 err := s.client.ServiceSet(params.ServiceSet{ 587 ServiceName: "dummy", 588 Options: map[string]string{ 589 "title": "foobar", 590 "username": "user name", 591 }}) 592 c.Assert(err, jc.ErrorIsNil) 593 settings, err := dummy.ConfigSettings() 594 c.Assert(err, jc.ErrorIsNil) 595 c.Assert(settings, gc.DeepEquals, charm.Settings{ 596 "title": "foobar", 597 "username": "user name", 598 }) 599 return dummy 600 } 601 602 func (s *serverSuite) assertServerUnsetBlocked(c *gc.C, blocked bool, dummy *state.Service) { 603 err := s.client.ServiceUnset(params.ServiceUnset{ 604 ServiceName: "dummy", 605 Options: []string{"username"}, 606 }) 607 if blocked { 608 c.Assert(errors.Cause(err), gc.DeepEquals, common.ErrOperationBlocked) 609 } else { 610 c.Assert(err, jc.ErrorIsNil) 611 settings, err := dummy.ConfigSettings() 612 c.Assert(err, jc.ErrorIsNil) 613 c.Assert(settings, gc.DeepEquals, charm.Settings{ 614 "title": "foobar", 615 }) 616 } 617 } 618 619 func (s *serverSuite) TestBlockDestroyServerUnset(c *gc.C) { 620 dummy := s.setupServerUnsetBlocked(c) 621 s.blockDestroyEnvironment(c) 622 s.assertServerUnsetBlocked(c, false, dummy) 623 } 624 625 func (s *serverSuite) TestBlockRemoveServerUnset(c *gc.C) { 626 dummy := s.setupServerUnsetBlocked(c) 627 s.blockRemoveObject(c) 628 s.assertServerUnsetBlocked(c, false, dummy) 629 } 630 631 func (s *serverSuite) TestBlockChangesServerUnset(c *gc.C) { 632 dummy := s.setupServerUnsetBlocked(c) 633 s.blockAllChanges(c) 634 s.assertServerUnsetBlocked(c, true, dummy) 635 } 636 637 func (s *clientSuite) TestClientServiceSetYAML(c *gc.C) { 638 dummy := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 639 640 err := s.APIState.Client().ServiceSetYAML("dummy", "dummy:\n title: foobar\n username: user name\n") 641 c.Assert(err, jc.ErrorIsNil) 642 settings, err := dummy.ConfigSettings() 643 c.Assert(err, jc.ErrorIsNil) 644 c.Assert(settings, gc.DeepEquals, charm.Settings{ 645 "title": "foobar", 646 "username": "user name", 647 }) 648 649 err = s.APIState.Client().ServiceSetYAML("dummy", "dummy:\n title: barfoo\n username: \n") 650 c.Assert(err, jc.ErrorIsNil) 651 settings, err = dummy.ConfigSettings() 652 c.Assert(err, jc.ErrorIsNil) 653 c.Assert(settings, gc.DeepEquals, charm.Settings{ 654 "title": "barfoo", 655 }) 656 } 657 658 func (s *clientSuite) assertServiceSetYAMLBlocked(c *gc.C, blocked bool, dummy *state.Service) { 659 err := s.APIState.Client().ServiceSetYAML("dummy", "dummy:\n title: foobar\n username: user name\n") 660 if blocked { 661 c.Assert(errors.Cause(err), gc.DeepEquals, common.ErrOperationBlocked) 662 } else { 663 c.Assert(err, jc.ErrorIsNil) 664 settings, err := dummy.ConfigSettings() 665 c.Assert(err, jc.ErrorIsNil) 666 c.Assert(settings, gc.DeepEquals, charm.Settings{ 667 "title": "foobar", 668 "username": "user name", 669 }) 670 } 671 } 672 673 func (s *clientSuite) TestBlockDestroyServiceSetYAML(c *gc.C) { 674 dummy := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 675 s.blockDestroyEnvironment(c) 676 s.assertServiceSetYAMLBlocked(c, false, dummy) 677 } 678 679 func (s *clientSuite) TestBlockRemoveServiceSetYAML(c *gc.C) { 680 dummy := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 681 s.blockRemoveObject(c) 682 s.assertServiceSetYAMLBlocked(c, false, dummy) 683 } 684 685 func (s *clientSuite) TestBlockChangesServiceSetYAML(c *gc.C) { 686 dummy := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 687 s.blockAllChanges(c) 688 s.assertServiceSetYAMLBlocked(c, true, dummy) 689 } 690 691 var clientAddServiceUnitsTests = []struct { 692 about string 693 service string // if not set, defaults to 'dummy' 694 expected []string 695 to string 696 err string 697 }{ 698 { 699 about: "returns unit names", 700 expected: []string{"dummy/0", "dummy/1", "dummy/2"}, 701 }, 702 { 703 about: "fails trying to add zero units", 704 err: "must add at least one unit", 705 }, 706 { 707 about: "cannot mix to when adding multiple units", 708 err: "cannot use NumUnits with ToMachineSpec", 709 expected: []string{"dummy/0", "dummy/1"}, 710 to: "0", 711 }, 712 { 713 // Note: chained-state, we add 1 unit here, but the 3 units 714 // from the first condition still exist 715 about: "force the unit onto bootstrap machine", 716 expected: []string{"dummy/3"}, 717 to: "0", 718 }, 719 { 720 about: "unknown service name", 721 service: "unknown-service", 722 err: `service "unknown-service" not found`, 723 }, 724 } 725 726 func (s *clientSuite) TestClientAddServiceUnits(c *gc.C) { 727 s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 728 for i, t := range clientAddServiceUnitsTests { 729 c.Logf("test %d. %s", i, t.about) 730 serviceName := t.service 731 if serviceName == "" { 732 serviceName = "dummy" 733 } 734 units, err := s.APIState.Client().AddServiceUnits(serviceName, len(t.expected), t.to) 735 if t.err != "" { 736 c.Assert(err, gc.ErrorMatches, t.err) 737 continue 738 } 739 c.Assert(err, jc.ErrorIsNil) 740 c.Assert(units, gc.DeepEquals, t.expected) 741 } 742 // Test that we actually assigned the unit to machine 0 743 forcedUnit, err := s.BackingState.Unit("dummy/3") 744 c.Assert(err, jc.ErrorIsNil) 745 assignedMachine, err := forcedUnit.AssignedMachineId() 746 c.Assert(err, jc.ErrorIsNil) 747 c.Assert(assignedMachine, gc.Equals, "0") 748 } 749 750 func (s *clientSuite) assertAddServiceUnitsBlocked(c *gc.C, blocked bool) { 751 units, err := s.APIState.Client().AddServiceUnits("dummy", 3, "") 752 if blocked { 753 c.Assert(errors.Cause(err), gc.DeepEquals, common.ErrOperationBlocked) 754 } else { 755 c.Assert(err, jc.ErrorIsNil) 756 c.Assert(units, gc.DeepEquals, []string{"dummy/0", "dummy/1", "dummy/2"}) 757 758 // Test that we actually assigned the unit to machine 0 759 forcedUnit, err := s.BackingState.Unit("dummy/0") 760 c.Assert(err, jc.ErrorIsNil) 761 assignedMachine, err := forcedUnit.AssignedMachineId() 762 c.Assert(err, jc.ErrorIsNil) 763 c.Assert(assignedMachine, gc.Equals, "0") 764 } 765 } 766 767 func (s *clientSuite) TestBlockDestroyAddServiceUnits(c *gc.C) { 768 s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 769 s.blockDestroyEnvironment(c) 770 s.assertAddServiceUnitsBlocked(c, false) 771 } 772 773 func (s *clientSuite) TestBlockRemoveAddServiceUnits(c *gc.C) { 774 s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 775 s.blockRemoveObject(c) 776 s.assertAddServiceUnitsBlocked(c, false) 777 } 778 779 func (s *clientSuite) TestBlockChangeAddServiceUnits(c *gc.C) { 780 s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 781 s.blockAllChanges(c) 782 s.assertAddServiceUnitsBlocked(c, true) 783 } 784 785 func (s *clientSuite) TestClientAddUnitToMachineNotFound(c *gc.C) { 786 s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 787 _, err := s.APIState.Client().AddServiceUnits("dummy", 1, "42") 788 c.Assert(err, gc.ErrorMatches, `cannot add units for service "dummy" to machine 42: machine 42 not found`) 789 } 790 791 func (s *clientSuite) TestClientCharmInfo(c *gc.C) { 792 var clientCharmInfoTests = []struct { 793 about string 794 charm string 795 url string 796 expectedActions *charm.Actions 797 err string 798 }{ 799 { 800 about: "dummy charm which contains an expectedActions spec", 801 charm: "dummy", 802 url: "local:quantal/dummy-1", 803 expectedActions: &charm.Actions{ 804 ActionSpecs: map[string]charm.ActionSpec{ 805 "snapshot": { 806 Description: "Take a snapshot of the database.", 807 Params: map[string]interface{}{ 808 "type": "object", 809 "title": "snapshot", 810 "description": "Take a snapshot of the database.", 811 "properties": map[string]interface{}{ 812 "outfile": map[string]interface{}{ 813 "default": "foo.bz2", 814 "description": "The file to write out to.", 815 "type": "string", 816 }, 817 }, 818 }, 819 }, 820 }, 821 }, 822 }, 823 { 824 about: "retrieves charm info", 825 // Use wordpress for tests so that we can compare Provides and Requires. 826 charm: "wordpress", 827 expectedActions: &charm.Actions{ActionSpecs: map[string]charm.ActionSpec{ 828 "fakeaction": { 829 Description: "No description", 830 Params: map[string]interface{}{ 831 "type": "object", 832 "title": "fakeaction", 833 "description": "No description", 834 "properties": map[string]interface{}{}, 835 }, 836 }, 837 }}, 838 url: "local:quantal/wordpress-3", 839 }, 840 { 841 about: "invalid URL", 842 charm: "wordpress", 843 expectedActions: &charm.Actions{ActionSpecs: nil}, 844 url: "not-valid", 845 err: "charm url series is not resolved", 846 }, 847 { 848 about: "invalid schema", 849 charm: "wordpress", 850 expectedActions: &charm.Actions{ActionSpecs: nil}, 851 url: "not-valid:your-arguments", 852 err: `charm URL has invalid schema: "not-valid:your-arguments"`, 853 }, 854 { 855 about: "unknown charm", 856 charm: "wordpress", 857 expectedActions: &charm.Actions{ActionSpecs: nil}, 858 url: "cs:missing/one-1", 859 err: `charm "cs:missing/one-1" not found`, 860 }, 861 } 862 863 for i, t := range clientCharmInfoTests { 864 c.Logf("test %d. %s", i, t.about) 865 charm := s.AddTestingCharm(c, t.charm) 866 info, err := s.APIState.Client().CharmInfo(t.url) 867 if t.err != "" { 868 c.Check(err, gc.ErrorMatches, t.err) 869 continue 870 } 871 c.Assert(err, jc.ErrorIsNil) 872 expected := &api.CharmInfo{ 873 Revision: charm.Revision(), 874 URL: charm.URL().String(), 875 Config: charm.Config(), 876 Meta: charm.Meta(), 877 Actions: charm.Actions(), 878 } 879 c.Check(info, jc.DeepEquals, expected) 880 c.Check(info.Actions, jc.DeepEquals, t.expectedActions) 881 } 882 } 883 884 func (s *clientSuite) TestClientEnvironmentInfo(c *gc.C) { 885 conf, _ := s.State.EnvironConfig() 886 info, err := s.APIState.Client().EnvironmentInfo() 887 c.Assert(err, jc.ErrorIsNil) 888 env, err := s.State.Environment() 889 c.Assert(err, jc.ErrorIsNil) 890 c.Assert(info.DefaultSeries, gc.Equals, config.PreferredSeries(conf)) 891 c.Assert(info.ProviderType, gc.Equals, conf.Type()) 892 c.Assert(info.Name, gc.Equals, conf.Name()) 893 c.Assert(info.UUID, gc.Equals, env.UUID()) 894 } 895 896 var clientAnnotationsTests = []struct { 897 about string 898 initial map[string]string 899 input map[string]string 900 expected map[string]string 901 err string 902 }{ 903 { 904 about: "test setting an annotation", 905 input: map[string]string{"mykey": "myvalue"}, 906 expected: map[string]string{"mykey": "myvalue"}, 907 }, 908 { 909 about: "test setting multiple annotations", 910 input: map[string]string{"key1": "value1", "key2": "value2"}, 911 expected: map[string]string{"key1": "value1", "key2": "value2"}, 912 }, 913 { 914 about: "test overriding annotations", 915 initial: map[string]string{"mykey": "myvalue"}, 916 input: map[string]string{"mykey": "another-value"}, 917 expected: map[string]string{"mykey": "another-value"}, 918 }, 919 { 920 about: "test setting an invalid annotation", 921 input: map[string]string{"invalid.key": "myvalue"}, 922 err: `cannot update annotations on .*: invalid key "invalid.key"`, 923 }, 924 } 925 926 func (s *clientSuite) TestClientAnnotations(c *gc.C) { 927 // Set up entities. 928 service := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 929 unit, err := service.AddUnit() 930 c.Assert(err, jc.ErrorIsNil) 931 machine, err := s.State.AddMachine("quantal", state.JobHostUnits) 932 c.Assert(err, jc.ErrorIsNil) 933 environment, err := s.State.Environment() 934 c.Assert(err, jc.ErrorIsNil) 935 type taggedAnnotator interface { 936 state.Entity 937 } 938 entities := []taggedAnnotator{service, unit, machine, environment} 939 for i, t := range clientAnnotationsTests { 940 for _, entity := range entities { 941 id := entity.Tag().String() // this is WRONG, it should be Tag().Id() but the code is wrong. 942 c.Logf("test %d. %s. entity %s", i, t.about, id) 943 // Set initial entity annotations. 944 err := s.APIState.Client().SetAnnotations(id, t.initial) 945 c.Assert(err, jc.ErrorIsNil) 946 // Add annotations using the API call. 947 err = s.APIState.Client().SetAnnotations(id, t.input) 948 if t.err != "" { 949 c.Assert(err, gc.ErrorMatches, t.err) 950 continue 951 } 952 // Retrieve annotations using the API call. 953 ann, err := s.APIState.Client().GetAnnotations(id) 954 c.Assert(err, jc.ErrorIsNil) 955 // Check annotations are correctly returned. 956 c.Assert(ann, gc.DeepEquals, t.input) 957 // Clean up annotations on the current entity. 958 cleanup := make(map[string]string) 959 for key := range ann { 960 cleanup[key] = "" 961 } 962 err = s.APIState.Client().SetAnnotations(id, cleanup) 963 c.Assert(err, jc.ErrorIsNil) 964 } 965 } 966 } 967 968 func (s *clientSuite) TestCharmAnnotationsUnsupported(c *gc.C) { 969 // Set up charm. 970 charm := s.AddTestingCharm(c, "dummy") 971 id := charm.Tag().Id() 972 for i, t := range clientAnnotationsTests { 973 c.Logf("test %d. %s. entity %s", i, t.about, id) 974 // Add annotations using the API call. 975 err := s.APIState.Client().SetAnnotations(id, t.input) 976 // Should not be able to annotate charm with this client 977 c.Assert(err.Error(), gc.Matches, ".*is not a valid tag.*") 978 979 // Retrieve annotations using the API call. 980 ann, err := s.APIState.Client().GetAnnotations(id) 981 // Should not be able to get annotations from charm using this client 982 c.Assert(err.Error(), gc.Matches, ".*is not a valid tag.*") 983 c.Assert(ann, gc.IsNil) 984 } 985 } 986 987 func (s *clientSuite) TestClientAnnotationsBadEntity(c *gc.C) { 988 bad := []string{"", "machine", "-foo", "foo-", "---", "machine-jim", "unit-123", "unit-foo", "service-", "service-foo/bar"} 989 expected := `".*" is not a valid( [a-z]+)? tag` 990 for _, id := range bad { 991 err := s.APIState.Client().SetAnnotations(id, map[string]string{"mykey": "myvalue"}) 992 c.Assert(err, gc.ErrorMatches, expected) 993 _, err = s.APIState.Client().GetAnnotations(id) 994 c.Assert(err, gc.ErrorMatches, expected) 995 } 996 } 997 998 var serviceExposeTests = []struct { 999 about string 1000 service string 1001 err string 1002 exposed bool 1003 }{ 1004 { 1005 about: "unknown service name", 1006 service: "unknown-service", 1007 err: `service "unknown-service" not found`, 1008 }, 1009 { 1010 about: "expose a service", 1011 service: "dummy-service", 1012 exposed: true, 1013 }, 1014 { 1015 about: "expose an already exposed service", 1016 service: "exposed-service", 1017 exposed: true, 1018 }, 1019 } 1020 1021 func (s *clientSuite) TestClientServiceExpose(c *gc.C) { 1022 charm := s.AddTestingCharm(c, "dummy") 1023 serviceNames := []string{"dummy-service", "exposed-service"} 1024 svcs := make([]*state.Service, len(serviceNames)) 1025 var err error 1026 for i, name := range serviceNames { 1027 svcs[i] = s.AddTestingService(c, name, charm) 1028 c.Assert(svcs[i].IsExposed(), jc.IsFalse) 1029 } 1030 err = svcs[1].SetExposed() 1031 c.Assert(err, jc.ErrorIsNil) 1032 c.Assert(svcs[1].IsExposed(), jc.IsTrue) 1033 for i, t := range serviceExposeTests { 1034 c.Logf("test %d. %s", i, t.about) 1035 err = s.APIState.Client().ServiceExpose(t.service) 1036 if t.err != "" { 1037 c.Assert(err, gc.ErrorMatches, t.err) 1038 } else { 1039 c.Assert(err, jc.ErrorIsNil) 1040 service, err := s.State.Service(t.service) 1041 c.Assert(err, jc.ErrorIsNil) 1042 c.Assert(service.IsExposed(), gc.Equals, t.exposed) 1043 } 1044 } 1045 } 1046 1047 func (s *clientSuite) setupServiceExpose(c *gc.C) { 1048 charm := s.AddTestingCharm(c, "dummy") 1049 serviceNames := []string{"dummy-service", "exposed-service"} 1050 svcs := make([]*state.Service, len(serviceNames)) 1051 var err error 1052 for i, name := range serviceNames { 1053 svcs[i] = s.AddTestingService(c, name, charm) 1054 c.Assert(svcs[i].IsExposed(), jc.IsFalse) 1055 } 1056 err = svcs[1].SetExposed() 1057 c.Assert(err, jc.ErrorIsNil) 1058 c.Assert(svcs[1].IsExposed(), jc.IsTrue) 1059 } 1060 1061 func (s *clientSuite) assertServiceExposeBlocked(c *gc.C, blocked bool) { 1062 for i, t := range serviceExposeTests { 1063 c.Logf("test %d. %s", i, t.about) 1064 err := s.APIState.Client().ServiceExpose(t.service) 1065 if blocked { 1066 c.Assert(errors.Cause(err), gc.DeepEquals, common.ErrOperationBlocked) 1067 } else { 1068 if t.err != "" { 1069 c.Assert(err, gc.ErrorMatches, t.err) 1070 } else { 1071 c.Assert(err, jc.ErrorIsNil) 1072 service, err := s.State.Service(t.service) 1073 c.Assert(err, jc.ErrorIsNil) 1074 c.Assert(service.IsExposed(), gc.Equals, t.exposed) 1075 } 1076 } 1077 } 1078 } 1079 1080 func (s *clientSuite) TestBlockDestroyServiceExpose(c *gc.C) { 1081 s.setupServiceExpose(c) 1082 s.blockDestroyEnvironment(c) 1083 s.assertServiceExposeBlocked(c, false) 1084 } 1085 1086 func (s *clientSuite) TestBlockRemoveServiceExpose(c *gc.C) { 1087 s.setupServiceExpose(c) 1088 s.blockRemoveObject(c) 1089 s.assertServiceExposeBlocked(c, false) 1090 } 1091 1092 func (s *clientSuite) TestBlockChangesServiceExpose(c *gc.C) { 1093 s.setupServiceExpose(c) 1094 s.blockAllChanges(c) 1095 s.assertServiceExposeBlocked(c, true) 1096 } 1097 1098 var serviceUnexposeTests = []struct { 1099 about string 1100 service string 1101 err string 1102 initial bool 1103 expected bool 1104 }{ 1105 { 1106 about: "unknown service name", 1107 service: "unknown-service", 1108 err: `service "unknown-service" not found`, 1109 }, 1110 { 1111 about: "unexpose a service", 1112 service: "dummy-service", 1113 initial: true, 1114 expected: false, 1115 }, 1116 { 1117 about: "unexpose an already unexposed service", 1118 service: "dummy-service", 1119 initial: false, 1120 expected: false, 1121 }, 1122 } 1123 1124 func (s *clientSuite) TestClientServiceUnexpose(c *gc.C) { 1125 charm := s.AddTestingCharm(c, "dummy") 1126 for i, t := range serviceUnexposeTests { 1127 c.Logf("test %d. %s", i, t.about) 1128 svc := s.AddTestingService(c, "dummy-service", charm) 1129 if t.initial { 1130 svc.SetExposed() 1131 } 1132 c.Assert(svc.IsExposed(), gc.Equals, t.initial) 1133 err := s.APIState.Client().ServiceUnexpose(t.service) 1134 if t.err == "" { 1135 c.Assert(err, jc.ErrorIsNil) 1136 svc.Refresh() 1137 c.Assert(svc.IsExposed(), gc.Equals, t.expected) 1138 } else { 1139 c.Assert(err, gc.ErrorMatches, t.err) 1140 } 1141 err = svc.Destroy() 1142 c.Assert(err, jc.ErrorIsNil) 1143 } 1144 } 1145 1146 func (s *clientSuite) setupServiceUnexpose(c *gc.C) *state.Service { 1147 charm := s.AddTestingCharm(c, "dummy") 1148 svc := s.AddTestingService(c, "dummy-service", charm) 1149 svc.SetExposed() 1150 c.Assert(svc.IsExposed(), gc.Equals, true) 1151 return svc 1152 } 1153 1154 func (s *clientSuite) assertServiceUnexposeBlocked(c *gc.C, blocked bool, svc *state.Service) { 1155 err := s.APIState.Client().ServiceUnexpose("dummy-service") 1156 if blocked { 1157 c.Assert(errors.Cause(err), gc.DeepEquals, common.ErrOperationBlocked) 1158 } else { 1159 c.Assert(err, jc.ErrorIsNil) 1160 svc.Refresh() 1161 c.Assert(svc.IsExposed(), gc.Equals, false) 1162 } 1163 err = svc.Destroy() 1164 c.Assert(err, jc.ErrorIsNil) 1165 } 1166 1167 func (s *clientSuite) TestBlockDestroyServiceUnexpose(c *gc.C) { 1168 svc := s.setupServiceUnexpose(c) 1169 s.blockDestroyEnvironment(c) 1170 s.assertServiceUnexposeBlocked(c, false, svc) 1171 } 1172 1173 func (s *clientSuite) TestBlockRemoveServiceUnexpose(c *gc.C) { 1174 svc := s.setupServiceUnexpose(c) 1175 s.blockRemoveObject(c) 1176 s.assertServiceUnexposeBlocked(c, false, svc) 1177 } 1178 1179 func (s *clientSuite) TestBlockChangesServiceUnexpose(c *gc.C) { 1180 svc := s.setupServiceUnexpose(c) 1181 s.blockAllChanges(c) 1182 s.assertServiceUnexposeBlocked(c, true, svc) 1183 } 1184 1185 var serviceDestroyTests = []struct { 1186 about string 1187 service string 1188 err string 1189 }{ 1190 { 1191 about: "unknown service name", 1192 service: "unknown-service", 1193 err: `service "unknown-service" not found`, 1194 }, 1195 { 1196 about: "destroy a service", 1197 service: "dummy-service", 1198 }, 1199 { 1200 about: "destroy an already destroyed service", 1201 service: "dummy-service", 1202 err: `service "dummy-service" not found`, 1203 }, 1204 } 1205 1206 func (s *clientSuite) TestClientServiceDestroy(c *gc.C) { 1207 s.AddTestingService(c, "dummy-service", s.AddTestingCharm(c, "dummy")) 1208 for i, t := range serviceDestroyTests { 1209 c.Logf("test %d. %s", i, t.about) 1210 err := s.APIState.Client().ServiceDestroy(t.service) 1211 if t.err != "" { 1212 c.Assert(err, gc.ErrorMatches, t.err) 1213 } else { 1214 c.Assert(err, jc.ErrorIsNil) 1215 } 1216 } 1217 1218 // Now do ServiceDestroy on a service with units. Destroy will 1219 // cause the service to be not-Alive, but will not remove its 1220 // document. 1221 s.setUpScenario(c) 1222 serviceName := "wordpress" 1223 service, err := s.State.Service(serviceName) 1224 c.Assert(err, jc.ErrorIsNil) 1225 err = s.APIState.Client().ServiceDestroy(serviceName) 1226 c.Assert(err, jc.ErrorIsNil) 1227 err = service.Refresh() 1228 c.Assert(err, jc.ErrorIsNil) 1229 c.Assert(service.Life(), gc.Not(gc.Equals), state.Alive) 1230 } 1231 1232 func assertLife(c *gc.C, entity state.Living, life state.Life) { 1233 err := entity.Refresh() 1234 c.Assert(err, jc.ErrorIsNil) 1235 c.Assert(entity.Life(), gc.Equals, life) 1236 } 1237 1238 func assertRemoved(c *gc.C, entity state.Living) { 1239 err := entity.Refresh() 1240 c.Assert(err, jc.Satisfies, errors.IsNotFound) 1241 } 1242 1243 func assertKill(c *gc.C, killer Killer) { 1244 c.Assert(killer.Kill(), gc.IsNil) 1245 } 1246 1247 func (s *clientSuite) setupDestroyMachinesTest(c *gc.C) (*state.Machine, *state.Machine, *state.Machine, *state.Unit) { 1248 m0, err := s.State.AddMachine("quantal", state.JobManageEnviron) 1249 c.Assert(err, jc.ErrorIsNil) 1250 m1, err := s.State.AddMachine("quantal", state.JobHostUnits) 1251 c.Assert(err, jc.ErrorIsNil) 1252 m2, err := s.State.AddMachine("quantal", state.JobHostUnits) 1253 c.Assert(err, jc.ErrorIsNil) 1254 1255 sch := s.AddTestingCharm(c, "wordpress") 1256 wordpress := s.AddTestingService(c, "wordpress", sch) 1257 u, err := wordpress.AddUnit() 1258 c.Assert(err, jc.ErrorIsNil) 1259 err = u.AssignToMachine(m1) 1260 c.Assert(err, jc.ErrorIsNil) 1261 1262 return m0, m1, m2, u 1263 } 1264 1265 func (s *clientSuite) TestDestroyMachines(c *gc.C) { 1266 m0, m1, m2, u := s.setupDestroyMachinesTest(c) 1267 s.assertDestroyMachineSuccess(c, u, m0, m1, m2) 1268 } 1269 1270 func (s *clientSuite) TestForceDestroyMachines(c *gc.C) { 1271 s.assertForceDestroyMachines(c) 1272 } 1273 1274 func (s *clientSuite) TestDestroyPrincipalUnits(c *gc.C) { 1275 wordpress := s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 1276 units := make([]*state.Unit, 5) 1277 for i := range units { 1278 unit, err := wordpress.AddUnit() 1279 c.Assert(err, jc.ErrorIsNil) 1280 err = unit.SetStatus(state.StatusActive, "", nil) 1281 c.Assert(err, jc.ErrorIsNil) 1282 units[i] = unit 1283 } 1284 s.assertDestroyPrincipalUnits(c, units) 1285 } 1286 1287 func (s *clientSuite) TestDestroySubordinateUnits(c *gc.C) { 1288 wordpress := s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 1289 wordpress0, err := wordpress.AddUnit() 1290 c.Assert(err, jc.ErrorIsNil) 1291 s.AddTestingService(c, "logging", s.AddTestingCharm(c, "logging")) 1292 eps, err := s.State.InferEndpoints("logging", "wordpress") 1293 c.Assert(err, jc.ErrorIsNil) 1294 rel, err := s.State.AddRelation(eps...) 1295 c.Assert(err, jc.ErrorIsNil) 1296 ru, err := rel.Unit(wordpress0) 1297 c.Assert(err, jc.ErrorIsNil) 1298 err = ru.EnterScope(nil) 1299 c.Assert(err, jc.ErrorIsNil) 1300 logging0, err := s.State.Unit("logging/0") 1301 c.Assert(err, jc.ErrorIsNil) 1302 1303 // Try to destroy the subordinate alone; check it fails. 1304 err = s.APIState.Client().DestroyServiceUnits("logging/0") 1305 c.Assert(err, gc.ErrorMatches, `no units were destroyed: unit "logging/0" is a subordinate`) 1306 assertLife(c, logging0, state.Alive) 1307 1308 s.assertDestroySubordinateUnits(c, wordpress0, logging0) 1309 } 1310 1311 func (s *clientSuite) testClientUnitResolved(c *gc.C, retry bool, expectedResolvedMode state.ResolvedMode) { 1312 // Setup: 1313 s.setUpScenario(c) 1314 u, err := s.State.Unit("wordpress/0") 1315 c.Assert(err, jc.ErrorIsNil) 1316 err = u.SetStatus(state.StatusError, "gaaah", nil) 1317 c.Assert(err, jc.ErrorIsNil) 1318 // Code under test: 1319 err = s.APIState.Client().Resolved("wordpress/0", retry) 1320 c.Assert(err, jc.ErrorIsNil) 1321 // Freshen the unit's state. 1322 err = u.Refresh() 1323 c.Assert(err, jc.ErrorIsNil) 1324 // And now the actual test assertions: we set the unit as resolved via 1325 // the API so it should have a resolved mode set. 1326 mode := u.Resolved() 1327 c.Assert(mode, gc.Equals, expectedResolvedMode) 1328 } 1329 1330 func (s *clientSuite) TestClientUnitResolved(c *gc.C) { 1331 s.testClientUnitResolved(c, false, state.ResolvedNoHooks) 1332 } 1333 1334 func (s *clientSuite) TestClientUnitResolvedRetry(c *gc.C) { 1335 s.testClientUnitResolved(c, true, state.ResolvedRetryHooks) 1336 } 1337 1338 func (s *clientSuite) setupResolved(c *gc.C) *state.Unit { 1339 s.setUpScenario(c) 1340 u, err := s.State.Unit("wordpress/0") 1341 c.Assert(err, jc.ErrorIsNil) 1342 err = u.SetStatus(state.StatusError, "gaaah", nil) 1343 c.Assert(err, jc.ErrorIsNil) 1344 return u 1345 } 1346 1347 func (s *clientSuite) assertResolvedBlocked(c *gc.C, blocked bool, u *state.Unit) { 1348 err := s.APIState.Client().Resolved("wordpress/0", true) 1349 if blocked { 1350 c.Assert(errors.Cause(err), gc.DeepEquals, common.ErrOperationBlocked) 1351 } else { 1352 c.Assert(err, jc.ErrorIsNil) 1353 // Freshen the unit's state. 1354 err = u.Refresh() 1355 c.Assert(err, jc.ErrorIsNil) 1356 // And now the actual test assertions: we set the unit as resolved via 1357 // the API so it should have a resolved mode set. 1358 mode := u.Resolved() 1359 c.Assert(mode, gc.Equals, state.ResolvedRetryHooks) 1360 } 1361 } 1362 1363 func (s *clientSuite) TestBlockDestroyUnitResolved(c *gc.C) { 1364 u := s.setupResolved(c) 1365 s.blockDestroyEnvironment(c) 1366 s.assertResolvedBlocked(c, false, u) 1367 } 1368 1369 func (s *clientSuite) TestBlockRemoveUnitResolved(c *gc.C) { 1370 u := s.setupResolved(c) 1371 s.blockRemoveObject(c) 1372 s.assertResolvedBlocked(c, false, u) 1373 } 1374 1375 func (s *clientSuite) TestBlockChangeUnitResolved(c *gc.C) { 1376 u := s.setupResolved(c) 1377 s.blockAllChanges(c) 1378 s.assertResolvedBlocked(c, true, u) 1379 } 1380 1381 func (s *clientSuite) TestClientServiceDeployCharmErrors(c *gc.C) { 1382 s.makeMockCharmStore() 1383 for url, expect := range map[string]string{ 1384 "wordpress": "charm url series is not resolved", 1385 "cs:wordpress": "charm url series is not resolved", 1386 "cs:precise/wordpress": "charm url must include revision", 1387 "cs:precise/wordpress-999999": `cannot download charm ".*": charm not found in mock store: cs:precise/wordpress-999999`, 1388 } { 1389 c.Logf("test %s", url) 1390 err := s.APIState.Client().ServiceDeploy( 1391 url, "service", 1, "", constraints.Value{}, "", 1392 ) 1393 c.Check(err, gc.ErrorMatches, expect) 1394 _, err = s.State.Service("service") 1395 c.Assert(err, jc.Satisfies, errors.IsNotFound) 1396 } 1397 } 1398 1399 func (s *clientSuite) TestClientServiceDeployWithNetworks(c *gc.C) { 1400 s.makeMockCharmStore() 1401 curl, bundle := addCharm(c, "dummy") 1402 cons := constraints.MustParse("mem=4G networks=^net3") 1403 1404 // Check for invalid network tags handling. 1405 err := s.APIState.Client().ServiceDeployWithNetworks( 1406 curl.String(), "service", 3, "", cons, "", 1407 []string{"net1", "net2"}, 1408 nil, 1409 ) 1410 c.Assert(err, gc.ErrorMatches, `"net1" is not a valid tag`) 1411 1412 err = s.APIState.Client().ServiceDeployWithNetworks( 1413 curl.String(), "service", 3, "", cons, "", 1414 []string{"network-net1", "network-net2"}, 1415 nil, 1416 ) 1417 c.Assert(err, jc.ErrorIsNil) 1418 service := s.assertPrincipalDeployed(c, "service", curl, false, bundle, cons) 1419 1420 networks, err := service.Networks() 1421 c.Assert(err, jc.ErrorIsNil) 1422 c.Assert(networks, gc.DeepEquals, []string{"net1", "net2"}) 1423 serviceCons, err := service.Constraints() 1424 c.Assert(err, jc.ErrorIsNil) 1425 c.Assert(serviceCons, gc.DeepEquals, cons) 1426 } 1427 1428 func (s *clientSuite) TestClientServiceDeployWithStorage(c *gc.C) { 1429 s.PatchEnvironment(osenv.JujuFeatureFlagEnvKey, "storage") 1430 featureflag.SetFlagsFromEnvironment(osenv.JujuFeatureFlagEnvKey) 1431 s.testClientServiceDeployWithStorage(c, true) 1432 } 1433 1434 func (s *clientSuite) TestClientServiceDeployWithStorageWithoutFeature(c *gc.C) { 1435 s.testClientServiceDeployWithStorage(c, false) 1436 } 1437 1438 func (s *clientSuite) testClientServiceDeployWithStorage(c *gc.C, expectConstraints bool) { 1439 s.makeMockCharmStore() 1440 curl, bundle := addCharm(c, "storage-block") 1441 storageConstraints := map[string]storage.Constraints{ 1442 "data": { 1443 Count: 1, 1444 Size: 1024, 1445 }, 1446 } 1447 1448 var cons constraints.Value 1449 err := s.APIState.Client().ServiceDeployWithNetworks( 1450 curl.String(), "service", 1, "", cons, "", nil, 1451 storageConstraints, 1452 ) 1453 c.Assert(err, jc.ErrorIsNil) 1454 service := s.assertPrincipalDeployed(c, "service", curl, false, bundle, cons) 1455 storageConstraintsOut, err := service.StorageConstraints() 1456 c.Assert(err, jc.ErrorIsNil) 1457 1458 if expectConstraints { 1459 c.Assert(storageConstraintsOut, gc.DeepEquals, map[string]state.StorageConstraints{ 1460 "data": { 1461 Count: 1, 1462 Size: 1024, 1463 }, 1464 }) 1465 } else { 1466 c.Assert(storageConstraintsOut, gc.HasLen, 0) 1467 } 1468 } 1469 1470 func (s *clientSuite) setupServiceDeploy(c *gc.C, args string) (*charm.URL, charm.Charm, constraints.Value) { 1471 s.makeMockCharmStore() 1472 curl, bundle := addCharm(c, "dummy") 1473 cons := constraints.MustParse(args) 1474 return curl, bundle, cons 1475 } 1476 1477 func (s *clientSuite) assertServiceDeployWithNetworksBlocked(c *gc.C, blocked bool, curl *charm.URL, bundle charm.Charm, cons constraints.Value) { 1478 err := s.APIState.Client().ServiceDeployWithNetworks( 1479 curl.String(), "service", 3, "", cons, "", 1480 []string{"network-net1", "network-net2"}, 1481 nil, 1482 ) 1483 if blocked { 1484 c.Assert(errors.Cause(err), gc.DeepEquals, common.ErrOperationBlocked) 1485 } else { 1486 c.Assert(err, jc.ErrorIsNil) 1487 service := s.assertPrincipalDeployed(c, "service", curl, false, bundle, cons) 1488 networks, err := service.Networks() 1489 c.Assert(err, jc.ErrorIsNil) 1490 c.Assert(networks, gc.DeepEquals, []string{"net1", "net2"}) 1491 serviceCons, err := service.Constraints() 1492 c.Assert(err, jc.ErrorIsNil) 1493 c.Assert(serviceCons, gc.DeepEquals, cons) 1494 } 1495 } 1496 1497 func (s *clientSuite) TestBlockDestroyServiceDeployWithNetworks(c *gc.C) { 1498 curl, bundle, cons := s.setupServiceDeploy(c, "mem=4G networks=^net3") 1499 s.blockDestroyEnvironment(c) 1500 s.assertServiceDeployWithNetworksBlocked(c, false, curl, bundle, cons) 1501 } 1502 1503 func (s *clientSuite) TestBlockRemoveServiceDeployWithNetworks(c *gc.C) { 1504 curl, bundle, cons := s.setupServiceDeploy(c, "mem=4G networks=^net3") 1505 s.blockRemoveObject(c) 1506 s.assertServiceDeployWithNetworksBlocked(c, false, curl, bundle, cons) 1507 } 1508 1509 func (s *clientSuite) TestBlockChangeServiceDeployWithNetworks(c *gc.C) { 1510 curl, bundle, cons := s.setupServiceDeploy(c, "mem=4G networks=^net3") 1511 s.blockAllChanges(c) 1512 s.assertServiceDeployWithNetworksBlocked(c, true, curl, bundle, cons) 1513 } 1514 1515 func (s *clientSuite) assertPrincipalDeployed(c *gc.C, serviceName string, curl *charm.URL, forced bool, bundle charm.Charm, cons constraints.Value) *state.Service { 1516 service, err := s.State.Service(serviceName) 1517 c.Assert(err, jc.ErrorIsNil) 1518 charm, force, err := service.Charm() 1519 c.Assert(err, jc.ErrorIsNil) 1520 c.Assert(force, gc.Equals, forced) 1521 c.Assert(charm.URL(), gc.DeepEquals, curl) 1522 c.Assert(charm.Meta(), gc.DeepEquals, bundle.Meta()) 1523 c.Assert(charm.Config(), gc.DeepEquals, bundle.Config()) 1524 1525 serviceCons, err := service.Constraints() 1526 c.Assert(err, jc.ErrorIsNil) 1527 c.Assert(serviceCons, gc.DeepEquals, cons) 1528 units, err := service.AllUnits() 1529 c.Assert(err, jc.ErrorIsNil) 1530 for _, unit := range units { 1531 mid, err := unit.AssignedMachineId() 1532 c.Assert(err, jc.ErrorIsNil) 1533 machine, err := s.State.Machine(mid) 1534 c.Assert(err, jc.ErrorIsNil) 1535 machineCons, err := machine.Constraints() 1536 c.Assert(err, jc.ErrorIsNil) 1537 c.Assert(machineCons, gc.DeepEquals, cons) 1538 } 1539 return service 1540 } 1541 1542 func (s *clientSuite) TestClientServiceDeployPrincipal(c *gc.C) { 1543 // TODO(fwereade): test ToMachineSpec directly on srvClient, when we 1544 // manage to extract it as a package and can thus do it conveniently. 1545 s.makeMockCharmStore() 1546 curl, bundle := addCharm(c, "dummy") 1547 mem4g := constraints.MustParse("mem=4G") 1548 err := s.APIState.Client().ServiceDeploy( 1549 curl.String(), "service", 3, "", mem4g, "", 1550 ) 1551 c.Assert(err, jc.ErrorIsNil) 1552 s.assertPrincipalDeployed(c, "service", curl, false, bundle, mem4g) 1553 } 1554 1555 func (s *clientSuite) assertServiceDeployPrincipalBlocked(c *gc.C, blocked bool, curl *charm.URL, bundle charm.Charm, mem4g constraints.Value) { 1556 err := s.APIState.Client().ServiceDeploy( 1557 curl.String(), "service", 3, "", mem4g, "", 1558 ) 1559 if blocked { 1560 c.Assert(errors.Cause(err), gc.DeepEquals, common.ErrOperationBlocked) 1561 } else { 1562 c.Assert(err, jc.ErrorIsNil) 1563 s.assertPrincipalDeployed(c, "service", curl, false, bundle, mem4g) 1564 } 1565 } 1566 1567 func (s *clientSuite) TestBlockDestroyServiceDeployPrincipal(c *gc.C) { 1568 curl, bundle, cons := s.setupServiceDeploy(c, "mem=4G") 1569 s.blockDestroyEnvironment(c) 1570 s.assertServiceDeployPrincipalBlocked(c, false, curl, bundle, cons) 1571 } 1572 1573 func (s *clientSuite) TestBlockRemoveServiceDeployPrincipal(c *gc.C) { 1574 curl, bundle, cons := s.setupServiceDeploy(c, "mem=4G") 1575 s.blockRemoveObject(c) 1576 s.assertServiceDeployPrincipalBlocked(c, false, curl, bundle, cons) 1577 } 1578 1579 func (s *clientSuite) TestBlockChangesServiceDeployPrincipal(c *gc.C) { 1580 curl, bundle, cons := s.setupServiceDeploy(c, "mem=4G") 1581 s.blockAllChanges(c) 1582 s.assertServiceDeployPrincipalBlocked(c, true, curl, bundle, cons) 1583 } 1584 1585 func (s *clientSuite) TestClientServiceDeploySubordinate(c *gc.C) { 1586 s.makeMockCharmStore() 1587 curl, bundle := addCharm(c, "logging") 1588 err := s.APIState.Client().ServiceDeploy( 1589 curl.String(), "service-name", 0, "", constraints.Value{}, "", 1590 ) 1591 service, err := s.State.Service("service-name") 1592 c.Assert(err, jc.ErrorIsNil) 1593 charm, force, err := service.Charm() 1594 c.Assert(err, jc.ErrorIsNil) 1595 c.Assert(force, jc.IsFalse) 1596 c.Assert(charm.URL(), gc.DeepEquals, curl) 1597 c.Assert(charm.Meta(), gc.DeepEquals, bundle.Meta()) 1598 c.Assert(charm.Config(), gc.DeepEquals, bundle.Config()) 1599 1600 units, err := service.AllUnits() 1601 c.Assert(err, jc.ErrorIsNil) 1602 c.Assert(units, gc.HasLen, 0) 1603 } 1604 1605 func (s *clientSuite) TestClientServiceDeployConfig(c *gc.C) { 1606 // TODO(fwereade): test Config/ConfigYAML handling directly on srvClient. 1607 // Can't be done cleanly until it's extracted similarly to Machiner. 1608 s.makeMockCharmStore() 1609 curl, _ := addCharm(c, "dummy") 1610 err := s.APIState.Client().ServiceDeploy( 1611 curl.String(), "service-name", 1, "service-name:\n username: fred", constraints.Value{}, "", 1612 ) 1613 c.Assert(err, jc.ErrorIsNil) 1614 service, err := s.State.Service("service-name") 1615 c.Assert(err, jc.ErrorIsNil) 1616 settings, err := service.ConfigSettings() 1617 c.Assert(err, jc.ErrorIsNil) 1618 c.Assert(settings, gc.DeepEquals, charm.Settings{"username": "fred"}) 1619 } 1620 1621 func (s *clientSuite) TestClientServiceDeployConfigError(c *gc.C) { 1622 // TODO(fwereade): test Config/ConfigYAML handling directly on srvClient. 1623 // Can't be done cleanly until it's extracted similarly to Machiner. 1624 s.makeMockCharmStore() 1625 curl, _ := addCharm(c, "dummy") 1626 err := s.APIState.Client().ServiceDeploy( 1627 curl.String(), "service-name", 1, "service-name:\n skill-level: fred", constraints.Value{}, "", 1628 ) 1629 c.Assert(err, gc.ErrorMatches, `option "skill-level" expected int, got "fred"`) 1630 _, err = s.State.Service("service-name") 1631 c.Assert(err, jc.Satisfies, errors.IsNotFound) 1632 } 1633 1634 func (s *clientSuite) TestClientServiceDeployToMachine(c *gc.C) { 1635 s.makeMockCharmStore() 1636 curl, bundle := addCharm(c, "dummy") 1637 1638 machine, err := s.State.AddMachine("precise", state.JobHostUnits) 1639 c.Assert(err, jc.ErrorIsNil) 1640 err = s.APIState.Client().ServiceDeploy( 1641 curl.String(), "service-name", 1, "service-name:\n username: fred", constraints.Value{}, machine.Id(), 1642 ) 1643 c.Assert(err, jc.ErrorIsNil) 1644 1645 service, err := s.State.Service("service-name") 1646 c.Assert(err, jc.ErrorIsNil) 1647 charm, force, err := service.Charm() 1648 c.Assert(err, jc.ErrorIsNil) 1649 c.Assert(force, jc.IsFalse) 1650 c.Assert(charm.URL(), gc.DeepEquals, curl) 1651 c.Assert(charm.Meta(), gc.DeepEquals, bundle.Meta()) 1652 c.Assert(charm.Config(), gc.DeepEquals, bundle.Config()) 1653 1654 units, err := service.AllUnits() 1655 c.Assert(err, jc.ErrorIsNil) 1656 c.Assert(units, gc.HasLen, 1) 1657 mid, err := units[0].AssignedMachineId() 1658 c.Assert(err, jc.ErrorIsNil) 1659 c.Assert(mid, gc.Equals, machine.Id()) 1660 } 1661 1662 func (s *clientSuite) TestClientServiceDeployToMachineNotFound(c *gc.C) { 1663 err := s.APIState.Client().ServiceDeploy( 1664 "cs:precise/service-name-1", "service-name", 1, "", constraints.Value{}, "42", 1665 ) 1666 c.Assert(err, gc.ErrorMatches, `cannot deploy "service-name" to machine 42: machine 42 not found`) 1667 1668 _, err = s.State.Service("service-name") 1669 c.Assert(err, gc.ErrorMatches, `service "service-name" not found`) 1670 } 1671 1672 func (s *clientSuite) TestClientServiceDeployServiceOwner(c *gc.C) { 1673 s.makeMockCharmStore() 1674 curl, _ := addCharm(c, "dummy") 1675 1676 user := s.Factory.MakeUser(c, &factory.UserParams{Password: "password"}) 1677 s.APIState = s.OpenAPIAs(c, user.Tag(), "password") 1678 1679 err := s.APIState.Client().ServiceDeploy( 1680 curl.String(), "service", 3, "", constraints.Value{}, "", 1681 ) 1682 c.Assert(err, jc.ErrorIsNil) 1683 1684 service, err := s.State.Service("service") 1685 c.Assert(err, jc.ErrorIsNil) 1686 c.Assert(service.GetOwnerTag(), gc.Equals, user.Tag().String()) 1687 } 1688 1689 func (s *clientSuite) deployServiceForTests(c *gc.C) { 1690 curl, _ := addCharm(c, "dummy") 1691 err := s.APIState.Client().ServiceDeploy(curl.String(), 1692 "service", 1, "", constraints.Value{}, "", 1693 ) 1694 c.Assert(err, jc.ErrorIsNil) 1695 } 1696 1697 func (s *clientSuite) checkClientServiceUpdateSetCharm(c *gc.C, forceCharmUrl bool) { 1698 s.makeMockCharmStore() 1699 s.deployServiceForTests(c) 1700 addCharm(c, "wordpress") 1701 1702 // Update the charm for the service. 1703 args := params.ServiceUpdate{ 1704 ServiceName: "service", 1705 CharmUrl: "cs:precise/wordpress-3", 1706 ForceCharmUrl: forceCharmUrl, 1707 } 1708 err := s.APIState.Client().ServiceUpdate(args) 1709 c.Assert(err, jc.ErrorIsNil) 1710 1711 // Ensure the charm has been updated and and the force flag correctly set. 1712 service, err := s.State.Service("service") 1713 c.Assert(err, jc.ErrorIsNil) 1714 ch, force, err := service.Charm() 1715 c.Assert(err, jc.ErrorIsNil) 1716 c.Assert(ch.URL().String(), gc.Equals, "cs:precise/wordpress-3") 1717 c.Assert(force, gc.Equals, forceCharmUrl) 1718 } 1719 1720 func (s *clientSuite) TestClientServiceUpdateSetCharm(c *gc.C) { 1721 s.checkClientServiceUpdateSetCharm(c, false) 1722 } 1723 1724 func (s *clientSuite) TestBlockDestroyServiceUpdate(c *gc.C) { 1725 s.blockDestroyEnvironment(c) 1726 s.checkClientServiceUpdateSetCharm(c, false) 1727 } 1728 1729 func (s *clientSuite) TestBlockRemoveServiceUpdate(c *gc.C) { 1730 s.blockRemoveObject(c) 1731 s.checkClientServiceUpdateSetCharm(c, false) 1732 } 1733 1734 func (s *clientSuite) setupServiceUpdate(c *gc.C) { 1735 s.makeMockCharmStore() 1736 s.deployServiceForTests(c) 1737 addCharm(c, "wordpress") 1738 } 1739 1740 func (s *clientSuite) TestBlockChangeServiceUpdate(c *gc.C) { 1741 s.setupServiceUpdate(c) 1742 s.blockAllChanges(c) 1743 // Update the charm for the service. 1744 args := params.ServiceUpdate{ 1745 ServiceName: "service", 1746 CharmUrl: "cs:precise/wordpress-3", 1747 ForceCharmUrl: false, 1748 } 1749 err := s.APIState.Client().ServiceUpdate(args) 1750 c.Assert(errors.Cause(err), gc.DeepEquals, common.ErrOperationBlocked) 1751 } 1752 1753 func (s *clientSuite) TestClientServiceUpdateForceSetCharm(c *gc.C) { 1754 s.checkClientServiceUpdateSetCharm(c, true) 1755 } 1756 1757 func (s *clientSuite) TestBlockServiceUpdateForced(c *gc.C) { 1758 s.setupServiceUpdate(c) 1759 1760 // block all changes. Force should ignore block :) 1761 s.blockAllChanges(c) 1762 s.blockDestroyEnvironment(c) 1763 s.blockRemoveObject(c) 1764 1765 // Update the charm for the service. 1766 args := params.ServiceUpdate{ 1767 ServiceName: "service", 1768 CharmUrl: "cs:precise/wordpress-3", 1769 ForceCharmUrl: true, 1770 } 1771 err := s.APIState.Client().ServiceUpdate(args) 1772 c.Assert(err, jc.ErrorIsNil) 1773 1774 // Ensure the charm has been updated and and the force flag correctly set. 1775 service, err := s.State.Service("service") 1776 c.Assert(err, jc.ErrorIsNil) 1777 ch, force, err := service.Charm() 1778 c.Assert(err, jc.ErrorIsNil) 1779 c.Assert(ch.URL().String(), gc.Equals, "cs:precise/wordpress-3") 1780 c.Assert(force, jc.IsTrue) 1781 } 1782 1783 func (s *clientSuite) TestClientServiceUpdateSetCharmErrors(c *gc.C) { 1784 s.makeMockCharmStore() 1785 s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 1786 for charmUrl, expect := range map[string]string{ 1787 "wordpress": "charm url series is not resolved", 1788 "cs:wordpress": "charm url series is not resolved", 1789 "cs:precise/wordpress": "charm url must include revision", 1790 "cs:precise/wordpress-999999": `cannot download charm ".*": charm not found in mock store: cs:precise/wordpress-999999`, 1791 } { 1792 c.Logf("test %s", charmUrl) 1793 args := params.ServiceUpdate{ 1794 ServiceName: "wordpress", 1795 CharmUrl: charmUrl, 1796 } 1797 err := s.APIState.Client().ServiceUpdate(args) 1798 c.Check(err, gc.ErrorMatches, expect) 1799 } 1800 } 1801 1802 func (s *clientSuite) TestClientServiceUpdateSetMinUnits(c *gc.C) { 1803 service := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 1804 1805 // Set minimum units for the service. 1806 minUnits := 2 1807 args := params.ServiceUpdate{ 1808 ServiceName: "dummy", 1809 MinUnits: &minUnits, 1810 } 1811 err := s.APIState.Client().ServiceUpdate(args) 1812 c.Assert(err, jc.ErrorIsNil) 1813 1814 // Ensure the minimum number of units has been set. 1815 c.Assert(service.Refresh(), gc.IsNil) 1816 c.Assert(service.MinUnits(), gc.Equals, minUnits) 1817 } 1818 1819 func (s *clientSuite) TestClientServiceUpdateSetMinUnitsError(c *gc.C) { 1820 service := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 1821 1822 // Set a negative minimum number of units for the service. 1823 minUnits := -1 1824 args := params.ServiceUpdate{ 1825 ServiceName: "dummy", 1826 MinUnits: &minUnits, 1827 } 1828 err := s.APIState.Client().ServiceUpdate(args) 1829 c.Assert(err, gc.ErrorMatches, 1830 `cannot set minimum units for service "dummy": cannot set a negative minimum number of units`) 1831 1832 // Ensure the minimum number of units has not been set. 1833 c.Assert(service.Refresh(), gc.IsNil) 1834 c.Assert(service.MinUnits(), gc.Equals, 0) 1835 } 1836 1837 func (s *clientSuite) TestClientServiceUpdateSetSettingsStrings(c *gc.C) { 1838 service := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 1839 1840 // Update settings for the service. 1841 args := params.ServiceUpdate{ 1842 ServiceName: "dummy", 1843 SettingsStrings: map[string]string{"title": "s-title", "username": "s-user"}, 1844 } 1845 err := s.APIState.Client().ServiceUpdate(args) 1846 c.Assert(err, jc.ErrorIsNil) 1847 1848 // Ensure the settings have been correctly updated. 1849 expected := charm.Settings{"title": "s-title", "username": "s-user"} 1850 obtained, err := service.ConfigSettings() 1851 c.Assert(err, jc.ErrorIsNil) 1852 c.Assert(obtained, gc.DeepEquals, expected) 1853 } 1854 1855 func (s *clientSuite) TestClientServiceUpdateSetSettingsYAML(c *gc.C) { 1856 service := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 1857 1858 // Update settings for the service. 1859 args := params.ServiceUpdate{ 1860 ServiceName: "dummy", 1861 SettingsYAML: "dummy:\n title: y-title\n username: y-user", 1862 } 1863 err := s.APIState.Client().ServiceUpdate(args) 1864 c.Assert(err, jc.ErrorIsNil) 1865 1866 // Ensure the settings have been correctly updated. 1867 expected := charm.Settings{"title": "y-title", "username": "y-user"} 1868 obtained, err := service.ConfigSettings() 1869 c.Assert(err, jc.ErrorIsNil) 1870 c.Assert(obtained, gc.DeepEquals, expected) 1871 } 1872 1873 func (s *clientSuite) TestClientServiceUpdateSetConstraints(c *gc.C) { 1874 service := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 1875 1876 // Update constraints for the service. 1877 cons, err := constraints.Parse("mem=4096", "cpu-cores=2") 1878 c.Assert(err, jc.ErrorIsNil) 1879 args := params.ServiceUpdate{ 1880 ServiceName: "dummy", 1881 Constraints: &cons, 1882 } 1883 err = s.APIState.Client().ServiceUpdate(args) 1884 c.Assert(err, jc.ErrorIsNil) 1885 1886 // Ensure the constraints have been correctly updated. 1887 obtained, err := service.Constraints() 1888 c.Assert(err, jc.ErrorIsNil) 1889 c.Assert(obtained, gc.DeepEquals, cons) 1890 } 1891 1892 func (s *clientSuite) TestClientServiceUpdateAllParams(c *gc.C) { 1893 s.makeMockCharmStore() 1894 s.deployServiceForTests(c) 1895 addCharm(c, "wordpress") 1896 1897 // Update all the service attributes. 1898 minUnits := 3 1899 cons, err := constraints.Parse("mem=4096", "cpu-cores=2") 1900 c.Assert(err, jc.ErrorIsNil) 1901 args := params.ServiceUpdate{ 1902 ServiceName: "service", 1903 CharmUrl: "cs:precise/wordpress-3", 1904 ForceCharmUrl: true, 1905 MinUnits: &minUnits, 1906 SettingsStrings: map[string]string{"blog-title": "string-title"}, 1907 SettingsYAML: "service:\n blog-title: yaml-title\n", 1908 Constraints: &cons, 1909 } 1910 err = s.APIState.Client().ServiceUpdate(args) 1911 c.Assert(err, jc.ErrorIsNil) 1912 1913 // Ensure the service has been correctly updated. 1914 service, err := s.State.Service("service") 1915 c.Assert(err, jc.ErrorIsNil) 1916 1917 // Check the charm. 1918 ch, force, err := service.Charm() 1919 c.Assert(err, jc.ErrorIsNil) 1920 c.Assert(ch.URL().String(), gc.Equals, "cs:precise/wordpress-3") 1921 c.Assert(force, jc.IsTrue) 1922 1923 // Check the minimum number of units. 1924 c.Assert(service.MinUnits(), gc.Equals, minUnits) 1925 1926 // Check the settings: also ensure the YAML settings take precedence 1927 // over strings ones. 1928 expectedSettings := charm.Settings{"blog-title": "yaml-title"} 1929 obtainedSettings, err := service.ConfigSettings() 1930 c.Assert(err, jc.ErrorIsNil) 1931 c.Assert(obtainedSettings, gc.DeepEquals, expectedSettings) 1932 1933 // Check the constraints. 1934 obtainedConstraints, err := service.Constraints() 1935 c.Assert(err, jc.ErrorIsNil) 1936 c.Assert(obtainedConstraints, gc.DeepEquals, cons) 1937 } 1938 1939 func (s *clientSuite) TestClientServiceUpdateNoParams(c *gc.C) { 1940 s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 1941 1942 // Calling ServiceUpdate with no parameters set is a no-op. 1943 args := params.ServiceUpdate{ServiceName: "wordpress"} 1944 err := s.APIState.Client().ServiceUpdate(args) 1945 c.Assert(err, jc.ErrorIsNil) 1946 } 1947 1948 func (s *clientSuite) TestClientServiceUpdateNoService(c *gc.C) { 1949 err := s.APIState.Client().ServiceUpdate(params.ServiceUpdate{}) 1950 c.Assert(err, gc.ErrorMatches, `"" is not a valid service name`) 1951 } 1952 1953 func (s *clientSuite) TestClientServiceUpdateInvalidService(c *gc.C) { 1954 args := params.ServiceUpdate{ServiceName: "no-such-service"} 1955 err := s.APIState.Client().ServiceUpdate(args) 1956 c.Assert(err, gc.ErrorMatches, `service "no-such-service" not found`) 1957 } 1958 1959 func (s *clientSuite) TestClientServiceSetCharm(c *gc.C) { 1960 s.makeMockCharmStore() 1961 curl, _ := addCharm(c, "dummy") 1962 err := s.APIState.Client().ServiceDeploy( 1963 curl.String(), "service", 3, "", constraints.Value{}, "", 1964 ) 1965 c.Assert(err, jc.ErrorIsNil) 1966 addCharm(c, "wordpress") 1967 err = s.APIState.Client().ServiceSetCharm( 1968 "service", "cs:precise/wordpress-3", false, 1969 ) 1970 c.Assert(err, jc.ErrorIsNil) 1971 1972 // Ensure that the charm is not marked as forced. 1973 service, err := s.State.Service("service") 1974 c.Assert(err, jc.ErrorIsNil) 1975 charm, force, err := service.Charm() 1976 c.Assert(err, jc.ErrorIsNil) 1977 c.Assert(charm.URL().String(), gc.Equals, "cs:precise/wordpress-3") 1978 c.Assert(force, jc.IsFalse) 1979 } 1980 1981 func (s *clientSuite) setupServiceSetCharm(c *gc.C) { 1982 s.makeMockCharmStore() 1983 curl, _ := addCharm(c, "dummy") 1984 err := s.APIState.Client().ServiceDeploy( 1985 curl.String(), "service", 3, "", constraints.Value{}, "", 1986 ) 1987 c.Assert(err, jc.ErrorIsNil) 1988 addCharm(c, "wordpress") 1989 } 1990 1991 func (s *clientSuite) assertServiceSetCharmBlocked(c *gc.C, blocked bool, force bool) { 1992 err := s.APIState.Client().ServiceSetCharm( 1993 "service", "cs:precise/wordpress-3", force, 1994 ) 1995 if blocked { 1996 c.Assert(errors.Cause(err), gc.DeepEquals, common.ErrOperationBlocked) 1997 } else { 1998 c.Assert(err, jc.ErrorIsNil) 1999 // Ensure that the charm is not marked as forced. 2000 service, err := s.State.Service("service") 2001 c.Assert(err, jc.ErrorIsNil) 2002 charm, _, err := service.Charm() 2003 c.Assert(err, jc.ErrorIsNil) 2004 c.Assert(charm.URL().String(), gc.Equals, "cs:precise/wordpress-3") 2005 } 2006 } 2007 2008 func (s *clientSuite) TestBlockDestroyServiceSetCharm(c *gc.C) { 2009 s.setupServiceSetCharm(c) 2010 s.blockDestroyEnvironment(c) 2011 s.assertServiceSetCharmBlocked(c, false, false) 2012 } 2013 2014 func (s *clientSuite) TestBlockRemoveServiceSetCharm(c *gc.C) { 2015 s.setupServiceSetCharm(c) 2016 s.blockRemoveObject(c) 2017 s.assertServiceSetCharmBlocked(c, false, false) 2018 } 2019 2020 func (s *clientSuite) TestBlockChangesServiceSetCharm(c *gc.C) { 2021 s.setupServiceSetCharm(c) 2022 s.blockAllChanges(c) 2023 s.assertServiceSetCharmBlocked(c, true, false) 2024 } 2025 2026 func (s *clientSuite) TestClientServiceSetCharmForce(c *gc.C) { 2027 s.makeMockCharmStore() 2028 curl, _ := addCharm(c, "dummy") 2029 err := s.APIState.Client().ServiceDeploy( 2030 curl.String(), "service", 3, "", constraints.Value{}, "", 2031 ) 2032 c.Assert(err, jc.ErrorIsNil) 2033 addCharm(c, "wordpress") 2034 err = s.APIState.Client().ServiceSetCharm( 2035 "service", "cs:precise/wordpress-3", true, 2036 ) 2037 c.Assert(err, jc.ErrorIsNil) 2038 2039 // Ensure that the charm is marked as forced. 2040 service, err := s.State.Service("service") 2041 c.Assert(err, jc.ErrorIsNil) 2042 charm, force, err := service.Charm() 2043 c.Assert(err, jc.ErrorIsNil) 2044 c.Assert(charm.URL().String(), gc.Equals, "cs:precise/wordpress-3") 2045 c.Assert(force, jc.IsTrue) 2046 } 2047 2048 func (s *clientSuite) TestBlockServiceSetCharmForce(c *gc.C) { 2049 s.setupServiceSetCharm(c) 2050 2051 // block all changes 2052 s.blockAllChanges(c) 2053 s.blockRemoveObject(c) 2054 s.blockDestroyEnvironment(c) 2055 2056 s.assertServiceSetCharmBlocked(c, false, true) 2057 } 2058 2059 func (s *clientSuite) TestClientServiceSetCharmInvalidService(c *gc.C) { 2060 s.makeMockCharmStore() 2061 err := s.APIState.Client().ServiceSetCharm( 2062 "badservice", "cs:precise/wordpress-3", true, 2063 ) 2064 c.Assert(err, gc.ErrorMatches, `service "badservice" not found`) 2065 } 2066 2067 func (s *clientSuite) TestClientServiceSetCharmErrors(c *gc.C) { 2068 s.makeMockCharmStore() 2069 s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 2070 for url, expect := range map[string]string{ 2071 // TODO(fwereade,Makyo) make these errors consistent one day. 2072 "wordpress": "charm url series is not resolved", 2073 "cs:wordpress": "charm url series is not resolved", 2074 "cs:precise/wordpress": "charm url must include revision", 2075 "cs:precise/wordpress-999999": `cannot download charm ".*": charm not found in mock store: cs:precise/wordpress-999999`, 2076 } { 2077 c.Logf("test %s", url) 2078 err := s.APIState.Client().ServiceSetCharm( 2079 "wordpress", url, false, 2080 ) 2081 c.Check(err, gc.ErrorMatches, expect) 2082 } 2083 } 2084 2085 func (s *clientSuite) makeMockCharmStore() (store *charmtesting.MockCharmStore) { 2086 mockStore := charmtesting.NewMockCharmStore() 2087 origStore := client.CharmStore 2088 client.CharmStore = mockStore 2089 s.AddCleanup(func(_ *gc.C) { client.CharmStore = origStore }) 2090 return mockStore 2091 } 2092 2093 func addCharm(c *gc.C, name string) (*charm.URL, charm.Charm) { 2094 return addSeriesCharm(c, "precise", name) 2095 } 2096 2097 func addSeriesCharm(c *gc.C, series, name string) (*charm.URL, charm.Charm) { 2098 bundle := testcharms.Repo.CharmArchive(c.MkDir(), name) 2099 scurl := fmt.Sprintf("cs:%s/%s-%d", series, name, bundle.Revision()) 2100 curl := charm.MustParseURL(scurl) 2101 err := client.CharmStore.(*charmtesting.MockCharmStore).SetCharm(curl, bundle) 2102 c.Assert(err, jc.ErrorIsNil) 2103 return curl, bundle 2104 } 2105 2106 func (s *clientSuite) checkEndpoints(c *gc.C, endpoints map[string]charm.Relation) { 2107 c.Assert(endpoints["wordpress"], gc.DeepEquals, charm.Relation{ 2108 Name: "db", 2109 Role: charm.RelationRole("requirer"), 2110 Interface: "mysql", 2111 Optional: false, 2112 Limit: 1, 2113 Scope: charm.RelationScope("global"), 2114 }) 2115 c.Assert(endpoints["mysql"], gc.DeepEquals, charm.Relation{ 2116 Name: "server", 2117 Role: charm.RelationRole("provider"), 2118 Interface: "mysql", 2119 Optional: false, 2120 Limit: 0, 2121 Scope: charm.RelationScope("global"), 2122 }) 2123 } 2124 2125 func (s *clientSuite) assertAddRelation(c *gc.C, endpoints []string) { 2126 s.setUpScenario(c) 2127 res, err := s.APIState.Client().AddRelation(endpoints...) 2128 c.Assert(err, jc.ErrorIsNil) 2129 s.checkEndpoints(c, res.Endpoints) 2130 // Show that the relation was added. 2131 wpSvc, err := s.State.Service("wordpress") 2132 c.Assert(err, jc.ErrorIsNil) 2133 rels, err := wpSvc.Relations() 2134 // There are 2 relations - the logging-wordpress one set up in the 2135 // scenario and the one created in this test. 2136 c.Assert(len(rels), gc.Equals, 2) 2137 mySvc, err := s.State.Service("mysql") 2138 c.Assert(err, jc.ErrorIsNil) 2139 rels, err = mySvc.Relations() 2140 c.Assert(len(rels), gc.Equals, 1) 2141 } 2142 2143 func (s *clientSuite) TestSuccessfullyAddRelation(c *gc.C) { 2144 endpoints := []string{"wordpress", "mysql"} 2145 s.assertAddRelation(c, endpoints) 2146 } 2147 2148 func (s *clientSuite) TestBlockDestroyAddRelation(c *gc.C) { 2149 s.blockDestroyEnvironment(c) 2150 s.assertAddRelation(c, []string{"wordpress", "mysql"}) 2151 } 2152 func (s *clientSuite) TestBlockRemoveAddRelation(c *gc.C) { 2153 s.blockRemoveObject(c) 2154 s.assertAddRelation(c, []string{"wordpress", "mysql"}) 2155 } 2156 2157 func (s *clientSuite) TestBlockChangesAddRelation(c *gc.C) { 2158 s.setUpScenario(c) 2159 s.blockAllChanges(c) 2160 _, err := s.APIState.Client().AddRelation([]string{"wordpress", "mysql"}...) 2161 c.Assert(errors.Cause(err), gc.DeepEquals, common.ErrOperationBlocked) 2162 } 2163 2164 func (s *clientSuite) TestSuccessfullyAddRelationSwapped(c *gc.C) { 2165 // Show that the order of the services listed in the AddRelation call 2166 // does not matter. This is a repeat of the previous test with the service 2167 // names swapped. 2168 endpoints := []string{"mysql", "wordpress"} 2169 s.assertAddRelation(c, endpoints) 2170 } 2171 2172 func (s *clientSuite) TestCallWithOnlyOneEndpoint(c *gc.C) { 2173 s.setUpScenario(c) 2174 endpoints := []string{"wordpress"} 2175 _, err := s.APIState.Client().AddRelation(endpoints...) 2176 c.Assert(err, gc.ErrorMatches, "no relations found") 2177 } 2178 2179 func (s *clientSuite) TestCallWithOneEndpointTooMany(c *gc.C) { 2180 s.setUpScenario(c) 2181 endpoints := []string{"wordpress", "mysql", "logging"} 2182 _, err := s.APIState.Client().AddRelation(endpoints...) 2183 c.Assert(err, gc.ErrorMatches, "cannot relate 3 endpoints") 2184 } 2185 2186 func (s *clientSuite) TestAddAlreadyAddedRelation(c *gc.C) { 2187 s.setUpScenario(c) 2188 // Add a relation between wordpress and mysql. 2189 endpoints := []string{"wordpress", "mysql"} 2190 eps, err := s.State.InferEndpoints(endpoints...) 2191 c.Assert(err, jc.ErrorIsNil) 2192 _, err = s.State.AddRelation(eps...) 2193 c.Assert(err, jc.ErrorIsNil) 2194 // And try to add it again. 2195 _, err = s.APIState.Client().AddRelation(endpoints...) 2196 c.Assert(err, gc.ErrorMatches, `cannot add relation "wordpress:db mysql:server": relation already exists`) 2197 } 2198 2199 func (s *clientSuite) setupRelationScenario(c *gc.C, endpoints []string) *state.Relation { 2200 s.setUpScenario(c) 2201 // Add a relation between the endpoints. 2202 eps, err := s.State.InferEndpoints(endpoints...) 2203 c.Assert(err, jc.ErrorIsNil) 2204 relation, err := s.State.AddRelation(eps...) 2205 c.Assert(err, jc.ErrorIsNil) 2206 return relation 2207 } 2208 2209 func (s *clientSuite) assertDestroyRelation(c *gc.C, endpoints []string) { 2210 s.assertDestroyRelationSuccess( 2211 c, 2212 s.setupRelationScenario(c, endpoints), 2213 endpoints) 2214 } 2215 2216 func (s *clientSuite) assertDestroyRelationSuccess(c *gc.C, relation *state.Relation, endpoints []string) { 2217 err := s.APIState.Client().DestroyRelation(endpoints...) 2218 c.Assert(err, jc.ErrorIsNil) 2219 // Show that the relation was removed. 2220 c.Assert(relation.Refresh(), jc.Satisfies, errors.IsNotFound) 2221 } 2222 2223 func (s *clientSuite) TestSuccessfulDestroyRelation(c *gc.C) { 2224 endpoints := []string{"wordpress", "mysql"} 2225 s.assertDestroyRelation(c, endpoints) 2226 } 2227 2228 func (s *clientSuite) TestSuccessfullyDestroyRelationSwapped(c *gc.C) { 2229 // Show that the order of the services listed in the DestroyRelation call 2230 // does not matter. This is a repeat of the previous test with the service 2231 // names swapped. 2232 endpoints := []string{"mysql", "wordpress"} 2233 s.assertDestroyRelation(c, endpoints) 2234 } 2235 2236 func (s *clientSuite) TestNoRelation(c *gc.C) { 2237 s.setUpScenario(c) 2238 endpoints := []string{"wordpress", "mysql"} 2239 err := s.APIState.Client().DestroyRelation(endpoints...) 2240 c.Assert(err, gc.ErrorMatches, `relation "wordpress:db mysql:server" not found`) 2241 } 2242 2243 func (s *clientSuite) TestAttemptDestroyingNonExistentRelation(c *gc.C) { 2244 s.setUpScenario(c) 2245 s.AddTestingService(c, "riak", s.AddTestingCharm(c, "riak")) 2246 endpoints := []string{"riak", "wordpress"} 2247 err := s.APIState.Client().DestroyRelation(endpoints...) 2248 c.Assert(err, gc.ErrorMatches, "no relations found") 2249 } 2250 2251 func (s *clientSuite) TestAttemptDestroyingWithOnlyOneEndpoint(c *gc.C) { 2252 s.setUpScenario(c) 2253 endpoints := []string{"wordpress"} 2254 err := s.APIState.Client().DestroyRelation(endpoints...) 2255 c.Assert(err, gc.ErrorMatches, "no relations found") 2256 } 2257 2258 func (s *clientSuite) TestAttemptDestroyingPeerRelation(c *gc.C) { 2259 s.setUpScenario(c) 2260 s.AddTestingService(c, "riak", s.AddTestingCharm(c, "riak")) 2261 2262 endpoints := []string{"riak:ring"} 2263 err := s.APIState.Client().DestroyRelation(endpoints...) 2264 c.Assert(err, gc.ErrorMatches, `cannot destroy relation "riak:ring": is a peer relation`) 2265 } 2266 2267 func (s *clientSuite) TestAttemptDestroyingAlreadyDestroyedRelation(c *gc.C) { 2268 s.setUpScenario(c) 2269 2270 // Add a relation between wordpress and mysql. 2271 eps, err := s.State.InferEndpoints("wordpress", "mysql") 2272 c.Assert(err, jc.ErrorIsNil) 2273 rel, err := s.State.AddRelation(eps...) 2274 c.Assert(err, jc.ErrorIsNil) 2275 2276 endpoints := []string{"wordpress", "mysql"} 2277 err = s.APIState.Client().DestroyRelation(endpoints...) 2278 // Show that the relation was removed. 2279 c.Assert(rel.Refresh(), jc.Satisfies, errors.IsNotFound) 2280 2281 // And try to destroy it again. 2282 err = s.APIState.Client().DestroyRelation(endpoints...) 2283 c.Assert(err, gc.ErrorMatches, `relation "wordpress:db mysql:server" not found`) 2284 } 2285 2286 func (s *clientSuite) TestClientWatchAll(c *gc.C) { 2287 // A very simple end-to-end test, because 2288 // all the logic is tested elsewhere. 2289 m, err := s.State.AddMachine("quantal", state.JobManageEnviron) 2290 c.Assert(err, jc.ErrorIsNil) 2291 err = m.SetProvisioned("i-0", agent.BootstrapNonce, nil) 2292 c.Assert(err, jc.ErrorIsNil) 2293 watcher, err := s.APIState.Client().WatchAll() 2294 c.Assert(err, jc.ErrorIsNil) 2295 defer func() { 2296 err := watcher.Stop() 2297 c.Assert(err, jc.ErrorIsNil) 2298 }() 2299 deltas, err := watcher.Next() 2300 c.Assert(err, jc.ErrorIsNil) 2301 if !c.Check(deltas, gc.DeepEquals, []multiwatcher.Delta{{ 2302 Entity: &multiwatcher.MachineInfo{ 2303 Id: m.Id(), 2304 InstanceId: "i-0", 2305 Status: multiwatcher.Status("pending"), 2306 Life: multiwatcher.Life("alive"), 2307 Series: "quantal", 2308 Jobs: []multiwatcher.MachineJob{state.JobManageEnviron.ToParams()}, 2309 Addresses: []network.Address{}, 2310 HardwareCharacteristics: &instance.HardwareCharacteristics{}, 2311 HasVote: false, 2312 WantsVote: true, 2313 }, 2314 }}) { 2315 c.Logf("got:") 2316 for _, d := range deltas { 2317 c.Logf("%#v\n", d.Entity) 2318 } 2319 } 2320 } 2321 2322 func (s *clientSuite) TestClientSetServiceConstraints(c *gc.C) { 2323 service := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 2324 2325 // Update constraints for the service. 2326 cons, err := constraints.Parse("mem=4096", "cpu-cores=2") 2327 c.Assert(err, jc.ErrorIsNil) 2328 err = s.APIState.Client().SetServiceConstraints("dummy", cons) 2329 c.Assert(err, jc.ErrorIsNil) 2330 2331 // Ensure the constraints have been correctly updated. 2332 obtained, err := service.Constraints() 2333 c.Assert(err, jc.ErrorIsNil) 2334 c.Assert(obtained, gc.DeepEquals, cons) 2335 } 2336 2337 func (s *clientSuite) setupSetServiceConstraints(c *gc.C) (*state.Service, constraints.Value) { 2338 service := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 2339 // Update constraints for the service. 2340 cons, err := constraints.Parse("mem=4096", "cpu-cores=2") 2341 c.Assert(err, jc.ErrorIsNil) 2342 return service, cons 2343 } 2344 2345 func (s *clientSuite) assertSetServiceConstraints(c *gc.C, blocked bool, service *state.Service, cons constraints.Value) { 2346 err := s.APIState.Client().SetServiceConstraints("dummy", cons) 2347 if blocked { 2348 c.Assert(errors.Cause(err), gc.DeepEquals, common.ErrOperationBlocked) 2349 } else { 2350 c.Assert(err, jc.ErrorIsNil) 2351 // Ensure the constraints have been correctly updated. 2352 obtained, err := service.Constraints() 2353 c.Assert(err, jc.ErrorIsNil) 2354 c.Assert(obtained, gc.DeepEquals, cons) 2355 } 2356 } 2357 2358 func (s *clientSuite) TestBlockDestroySetServiceConstraints(c *gc.C) { 2359 svc, cons := s.setupSetServiceConstraints(c) 2360 s.blockDestroyEnvironment(c) 2361 s.assertSetServiceConstraints(c, false, svc, cons) 2362 } 2363 2364 func (s *clientSuite) TestBlockRemoveSetServiceConstraints(c *gc.C) { 2365 svc, cons := s.setupSetServiceConstraints(c) 2366 s.blockRemoveObject(c) 2367 s.assertSetServiceConstraints(c, false, svc, cons) 2368 } 2369 2370 func (s *clientSuite) TestBlockChangesSetServiceConstraints(c *gc.C) { 2371 svc, cons := s.setupSetServiceConstraints(c) 2372 s.blockAllChanges(c) 2373 s.assertSetServiceConstraints(c, true, svc, cons) 2374 } 2375 2376 func (s *clientSuite) TestClientGetServiceConstraints(c *gc.C) { 2377 service := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 2378 2379 // Set constraints for the service. 2380 cons, err := constraints.Parse("mem=4096", "cpu-cores=2") 2381 c.Assert(err, jc.ErrorIsNil) 2382 err = service.SetConstraints(cons) 2383 c.Assert(err, jc.ErrorIsNil) 2384 2385 // Check we can get the constraints. 2386 obtained, err := s.APIState.Client().GetServiceConstraints("dummy") 2387 c.Assert(err, jc.ErrorIsNil) 2388 c.Assert(obtained, gc.DeepEquals, cons) 2389 } 2390 2391 func (s *clientSuite) TestClientSetEnvironmentConstraints(c *gc.C) { 2392 // Set constraints for the environment. 2393 cons, err := constraints.Parse("mem=4096", "cpu-cores=2") 2394 c.Assert(err, jc.ErrorIsNil) 2395 err = s.APIState.Client().SetEnvironmentConstraints(cons) 2396 c.Assert(err, jc.ErrorIsNil) 2397 2398 // Ensure the constraints have been correctly updated. 2399 obtained, err := s.State.EnvironConstraints() 2400 c.Assert(err, jc.ErrorIsNil) 2401 c.Assert(obtained, gc.DeepEquals, cons) 2402 } 2403 2404 func (s *clientSuite) assertSetEnvironmentConstraintsBlocked(c *gc.C, blocked bool) { 2405 // Set constraints for the environment. 2406 cons, err := constraints.Parse("mem=4096", "cpu-cores=2") 2407 c.Assert(err, jc.ErrorIsNil) 2408 err = s.APIState.Client().SetEnvironmentConstraints(cons) 2409 if blocked { 2410 c.Assert(errors.Cause(err), gc.DeepEquals, common.ErrOperationBlocked) 2411 } else { 2412 c.Assert(err, jc.ErrorIsNil) 2413 // Ensure the constraints have been correctly updated. 2414 obtained, err := s.State.EnvironConstraints() 2415 c.Assert(err, jc.ErrorIsNil) 2416 c.Assert(obtained, gc.DeepEquals, cons) 2417 } 2418 } 2419 2420 func (s *clientSuite) TestBlockDestroyClientSetEnvironmentConstraints(c *gc.C) { 2421 s.blockDestroyEnvironment(c) 2422 s.assertSetEnvironmentConstraintsBlocked(c, false) 2423 } 2424 2425 func (s *clientSuite) TestBlockRemoveClientSetEnvironmentConstraints(c *gc.C) { 2426 s.blockRemoveObject(c) 2427 s.assertSetEnvironmentConstraintsBlocked(c, false) 2428 } 2429 2430 func (s *clientSuite) TestBlockChangesClientSetEnvironmentConstraints(c *gc.C) { 2431 s.blockAllChanges(c) 2432 s.assertSetEnvironmentConstraintsBlocked(c, true) 2433 } 2434 2435 func (s *clientSuite) TestClientGetEnvironmentConstraints(c *gc.C) { 2436 // Set constraints for the environment. 2437 cons, err := constraints.Parse("mem=4096", "cpu-cores=2") 2438 c.Assert(err, jc.ErrorIsNil) 2439 err = s.State.SetEnvironConstraints(cons) 2440 c.Assert(err, jc.ErrorIsNil) 2441 2442 // Check we can get the constraints. 2443 obtained, err := s.APIState.Client().GetEnvironmentConstraints() 2444 c.Assert(err, jc.ErrorIsNil) 2445 c.Assert(obtained, gc.DeepEquals, cons) 2446 } 2447 2448 func (s *clientSuite) TestClientServiceCharmRelations(c *gc.C) { 2449 s.setUpScenario(c) 2450 _, err := s.APIState.Client().ServiceCharmRelations("blah") 2451 c.Assert(err, gc.ErrorMatches, `service "blah" not found`) 2452 2453 relations, err := s.APIState.Client().ServiceCharmRelations("wordpress") 2454 c.Assert(err, jc.ErrorIsNil) 2455 c.Assert(relations, gc.DeepEquals, []string{ 2456 "cache", "db", "juju-info", "logging-dir", "monitoring-port", "url", 2457 }) 2458 } 2459 2460 func (s *clientSuite) TestClientPublicAddressErrors(c *gc.C) { 2461 s.setUpScenario(c) 2462 _, err := s.APIState.Client().PublicAddress("wordpress") 2463 c.Assert(err, gc.ErrorMatches, `unknown unit or machine "wordpress"`) 2464 _, err = s.APIState.Client().PublicAddress("0") 2465 c.Assert(err, gc.ErrorMatches, `machine "0" has no public address`) 2466 _, err = s.APIState.Client().PublicAddress("wordpress/0") 2467 c.Assert(err, gc.ErrorMatches, `unit "wordpress/0" has no public address`) 2468 } 2469 2470 func (s *clientSuite) TestClientPublicAddressMachine(c *gc.C) { 2471 s.setUpScenario(c) 2472 2473 // Internally, network.SelectPublicAddress is used; the "most public" 2474 // address is returned. 2475 m1, err := s.State.Machine("1") 2476 c.Assert(err, jc.ErrorIsNil) 2477 cloudLocalAddress := network.NewAddress("cloudlocal", network.ScopeCloudLocal) 2478 publicAddress := network.NewAddress("public", network.ScopePublic) 2479 err = m1.SetAddresses(cloudLocalAddress) 2480 c.Assert(err, jc.ErrorIsNil) 2481 addr, err := s.APIState.Client().PublicAddress("1") 2482 c.Assert(err, jc.ErrorIsNil) 2483 c.Assert(addr, gc.Equals, "cloudlocal") 2484 err = m1.SetAddresses(cloudLocalAddress, publicAddress) 2485 addr, err = s.APIState.Client().PublicAddress("1") 2486 c.Assert(err, jc.ErrorIsNil) 2487 c.Assert(addr, gc.Equals, "public") 2488 } 2489 2490 func (s *clientSuite) TestClientPublicAddressUnit(c *gc.C) { 2491 s.setUpScenario(c) 2492 2493 m1, err := s.State.Machine("1") 2494 publicAddress := network.NewAddress("public", network.ScopePublic) 2495 err = m1.SetAddresses(publicAddress) 2496 c.Assert(err, jc.ErrorIsNil) 2497 addr, err := s.APIState.Client().PublicAddress("wordpress/0") 2498 c.Assert(err, jc.ErrorIsNil) 2499 c.Assert(addr, gc.Equals, "public") 2500 } 2501 2502 func (s *clientSuite) TestClientPrivateAddressErrors(c *gc.C) { 2503 s.setUpScenario(c) 2504 _, err := s.APIState.Client().PrivateAddress("wordpress") 2505 c.Assert(err, gc.ErrorMatches, `unknown unit or machine "wordpress"`) 2506 _, err = s.APIState.Client().PrivateAddress("0") 2507 c.Assert(err, gc.ErrorMatches, `machine "0" has no internal address`) 2508 _, err = s.APIState.Client().PrivateAddress("wordpress/0") 2509 c.Assert(err, gc.ErrorMatches, `unit "wordpress/0" has no internal address`) 2510 } 2511 2512 func (s *clientSuite) TestClientPrivateAddress(c *gc.C) { 2513 s.setUpScenario(c) 2514 2515 // Internally, network.SelectInternalAddress is used; the public 2516 // address if no cloud-local one is available. 2517 m1, err := s.State.Machine("1") 2518 c.Assert(err, jc.ErrorIsNil) 2519 cloudLocalAddress := network.NewAddress("cloudlocal", network.ScopeCloudLocal) 2520 publicAddress := network.NewAddress("public", network.ScopePublic) 2521 err = m1.SetAddresses(publicAddress) 2522 c.Assert(err, jc.ErrorIsNil) 2523 addr, err := s.APIState.Client().PrivateAddress("1") 2524 c.Assert(err, jc.ErrorIsNil) 2525 c.Assert(addr, gc.Equals, "public") 2526 err = m1.SetAddresses(cloudLocalAddress, publicAddress) 2527 addr, err = s.APIState.Client().PrivateAddress("1") 2528 c.Assert(err, jc.ErrorIsNil) 2529 c.Assert(addr, gc.Equals, "cloudlocal") 2530 } 2531 2532 func (s *clientSuite) TestClientPrivateAddressUnit(c *gc.C) { 2533 s.setUpScenario(c) 2534 2535 m1, err := s.State.Machine("1") 2536 privateAddress := network.NewAddress("private", network.ScopeCloudLocal) 2537 err = m1.SetAddresses(privateAddress) 2538 c.Assert(err, jc.ErrorIsNil) 2539 addr, err := s.APIState.Client().PrivateAddress("wordpress/0") 2540 c.Assert(err, jc.ErrorIsNil) 2541 c.Assert(addr, gc.Equals, "private") 2542 } 2543 2544 func (s *serverSuite) TestClientEnvironmentGet(c *gc.C) { 2545 envConfig, err := s.State.EnvironConfig() 2546 c.Assert(err, jc.ErrorIsNil) 2547 result, err := s.client.EnvironmentGet() 2548 c.Assert(err, jc.ErrorIsNil) 2549 c.Assert(result.Config, gc.DeepEquals, envConfig.AllAttrs()) 2550 } 2551 2552 func (s *serverSuite) assertEnvValue(c *gc.C, key string, expected interface{}) { 2553 envConfig, err := s.State.EnvironConfig() 2554 c.Assert(err, jc.ErrorIsNil) 2555 value, found := envConfig.AllAttrs()[key] 2556 c.Assert(found, jc.IsTrue) 2557 c.Assert(value, gc.Equals, expected) 2558 } 2559 2560 func (s *serverSuite) assertEnvValueMissing(c *gc.C, key string) { 2561 envConfig, err := s.State.EnvironConfig() 2562 c.Assert(err, jc.ErrorIsNil) 2563 _, found := envConfig.AllAttrs()[key] 2564 c.Assert(found, jc.IsFalse) 2565 } 2566 2567 func (s *serverSuite) TestClientEnvironmentSet(c *gc.C) { 2568 envConfig, err := s.State.EnvironConfig() 2569 c.Assert(err, jc.ErrorIsNil) 2570 _, found := envConfig.AllAttrs()["some-key"] 2571 c.Assert(found, jc.IsFalse) 2572 2573 params := params.EnvironmentSet{ 2574 Config: map[string]interface{}{ 2575 "some-key": "value", 2576 "other-key": "other value"}, 2577 } 2578 err = s.client.EnvironmentSet(params) 2579 c.Assert(err, jc.ErrorIsNil) 2580 s.assertEnvValue(c, "some-key", "value") 2581 s.assertEnvValue(c, "other-key", "other value") 2582 } 2583 2584 func (s *serverSuite) TestClientEnvironmentSetImmutable(c *gc.C) { 2585 // The various immutable config values are tested in 2586 // environs/config/config_test.go, so just choosing one here. 2587 params := params.EnvironmentSet{ 2588 Config: map[string]interface{}{"state-port": "1"}, 2589 } 2590 err := s.client.EnvironmentSet(params) 2591 c.Check(err, gc.ErrorMatches, `cannot change state-port from .* to 1`) 2592 } 2593 2594 func (s *serverSuite) assertEnvironmentSetBlocked(c *gc.C, args map[string]interface{}) { 2595 err := s.client.EnvironmentSet(params.EnvironmentSet{args}) 2596 c.Assert(errors.Cause(err), gc.DeepEquals, common.ErrOperationBlocked) 2597 } 2598 2599 func (s *serverSuite) assertEnvironmentSetNotBlocked(c *gc.C, args map[string]interface{}) { 2600 err := s.client.EnvironmentSet(params.EnvironmentSet{args}) 2601 c.Assert(err, jc.ErrorIsNil) 2602 s.assertEnvValue(c, "some-key", "value") 2603 } 2604 2605 func (s *serverSuite) TestBlockChangesClientEnvironmentSet(c *gc.C) { 2606 s.blockAllChanges(c) 2607 args := map[string]interface{}{"some-key": "value"} 2608 s.assertEnvironmentSetBlocked(c, args) 2609 2610 // Make sure just mentioning variable does not unblock env. 2611 // Need right value to unblock properly. 2612 args[config.PreventAllChangesKey] = true 2613 s.assertEnvironmentSetBlocked(c, args) 2614 2615 // But make sure that can unblock block-changes with right value. 2616 args[config.PreventAllChangesKey] = false 2617 s.assertEnvironmentSetNotBlocked(c, args) 2618 s.assertEnvValue(c, config.PreventAllChangesKey, false) 2619 } 2620 2621 func (s *serverSuite) TestClientEnvironmentSetDeprecated(c *gc.C) { 2622 envConfig, err := s.State.EnvironConfig() 2623 c.Assert(err, jc.ErrorIsNil) 2624 url := envConfig.AllAttrs()["agent-metadata-url"] 2625 c.Assert(url, gc.Equals, "") 2626 2627 args := params.EnvironmentSet{ 2628 Config: map[string]interface{}{"tools-metadata-url": "value"}, 2629 } 2630 err = s.client.EnvironmentSet(args) 2631 c.Assert(err, jc.ErrorIsNil) 2632 s.assertEnvValue(c, "agent-metadata-url", "value") 2633 s.assertEnvValue(c, "tools-metadata-url", "value") 2634 } 2635 2636 func (s *serverSuite) TestClientEnvironmentSetCannotChangeAgentVersion(c *gc.C) { 2637 args := params.EnvironmentSet{ 2638 map[string]interface{}{"agent-version": "9.9.9"}, 2639 } 2640 err := s.client.EnvironmentSet(args) 2641 c.Assert(err, gc.ErrorMatches, "agent-version cannot be changed") 2642 2643 // It's okay to pass env back with the same agent-version. 2644 result, err := s.client.EnvironmentGet() 2645 c.Assert(err, jc.ErrorIsNil) 2646 c.Assert(result.Config["agent-version"], gc.NotNil) 2647 args.Config["agent-version"] = result.Config["agent-version"] 2648 err = s.client.EnvironmentSet(args) 2649 c.Assert(err, jc.ErrorIsNil) 2650 } 2651 2652 func (s *serverSuite) TestClientEnvironmentUnset(c *gc.C) { 2653 err := s.State.UpdateEnvironConfig(map[string]interface{}{"abc": 123}, nil, nil) 2654 c.Assert(err, jc.ErrorIsNil) 2655 2656 args := params.EnvironmentUnset{[]string{"abc"}} 2657 err = s.client.EnvironmentUnset(args) 2658 c.Assert(err, jc.ErrorIsNil) 2659 s.assertEnvValueMissing(c, "abc") 2660 } 2661 2662 func (s *serverSuite) TestBlockClientEnvironmentUnset(c *gc.C) { 2663 err := s.State.UpdateEnvironConfig(map[string]interface{}{"abc": 123}, nil, nil) 2664 c.Assert(err, jc.ErrorIsNil) 2665 s.blockAllChanges(c) 2666 2667 args := params.EnvironmentUnset{[]string{"abc"}} 2668 err = s.client.EnvironmentUnset(args) 2669 c.Assert(errors.Cause(err), gc.Equals, common.ErrOperationBlocked) 2670 } 2671 2672 func (s *serverSuite) TestClientEnvironmentUnsetMissing(c *gc.C) { 2673 // It's okay to unset a non-existent attribute. 2674 args := params.EnvironmentUnset{[]string{"not_there"}} 2675 err := s.client.EnvironmentUnset(args) 2676 c.Assert(err, jc.ErrorIsNil) 2677 } 2678 2679 func (s *serverSuite) TestClientEnvironmentUnsetError(c *gc.C) { 2680 err := s.State.UpdateEnvironConfig(map[string]interface{}{"abc": 123}, nil, nil) 2681 c.Assert(err, jc.ErrorIsNil) 2682 2683 // "type" may not be removed, and this will cause an error. 2684 // If any one attribute's removal causes an error, there 2685 // should be no change. 2686 args := params.EnvironmentUnset{[]string{"abc", "type"}} 2687 err = s.client.EnvironmentUnset(args) 2688 c.Assert(err, gc.ErrorMatches, "type: expected string, got nothing") 2689 s.assertEnvValue(c, "abc", 123) 2690 } 2691 2692 func (s *clientSuite) TestClientFindTools(c *gc.C) { 2693 result, err := s.APIState.Client().FindTools(2, -1, "", "") 2694 c.Assert(err, jc.ErrorIsNil) 2695 c.Assert(result.Error, jc.Satisfies, params.IsCodeNotFound) 2696 toolstesting.UploadToStorage(c, s.DefaultToolsStorage, "released", version.MustParseBinary("2.12.0-precise-amd64")) 2697 result, err = s.APIState.Client().FindTools(2, 12, "precise", "amd64") 2698 c.Assert(err, jc.ErrorIsNil) 2699 c.Assert(result.Error, gc.IsNil) 2700 c.Assert(result.List, gc.HasLen, 1) 2701 c.Assert(result.List[0].Version, gc.Equals, version.MustParseBinary("2.12.0-precise-amd64")) 2702 url := fmt.Sprintf("https://%s/environment/%s/tools/%s", 2703 s.APIState.Addr(), coretesting.EnvironmentTag.Id(), result.List[0].Version) 2704 c.Assert(result.List[0].URL, gc.Equals, url) 2705 } 2706 2707 func (s *clientSuite) checkMachine(c *gc.C, id, series, cons string) { 2708 // Ensure the machine was actually created. 2709 machine, err := s.BackingState.Machine(id) 2710 c.Assert(err, jc.ErrorIsNil) 2711 c.Assert(machine.Series(), gc.Equals, series) 2712 c.Assert(machine.Jobs(), gc.DeepEquals, []state.MachineJob{state.JobHostUnits}) 2713 machineConstraints, err := machine.Constraints() 2714 c.Assert(err, jc.ErrorIsNil) 2715 c.Assert(machineConstraints.String(), gc.Equals, cons) 2716 } 2717 2718 func (s *clientSuite) TestClientAddMachinesDefaultSeries(c *gc.C) { 2719 apiParams := make([]params.AddMachineParams, 3) 2720 for i := 0; i < 3; i++ { 2721 apiParams[i] = params.AddMachineParams{ 2722 Jobs: []multiwatcher.MachineJob{multiwatcher.JobHostUnits}, 2723 } 2724 } 2725 machines, err := s.APIState.Client().AddMachines(apiParams) 2726 c.Assert(err, jc.ErrorIsNil) 2727 c.Assert(len(machines), gc.Equals, 3) 2728 for i, machineResult := range machines { 2729 c.Assert(machineResult.Machine, gc.DeepEquals, strconv.Itoa(i)) 2730 s.checkMachine(c, machineResult.Machine, coretesting.FakeDefaultSeries, apiParams[i].Constraints.String()) 2731 } 2732 } 2733 2734 func (s *clientSuite) assertAddMachinesBlocked(c *gc.C, blocked bool) { 2735 apiParams := make([]params.AddMachineParams, 3) 2736 for i := 0; i < 3; i++ { 2737 apiParams[i] = params.AddMachineParams{ 2738 Jobs: []multiwatcher.MachineJob{multiwatcher.JobHostUnits}, 2739 } 2740 } 2741 machines, err := s.APIState.Client().AddMachines(apiParams) 2742 if blocked { 2743 c.Assert(errors.Cause(err), gc.DeepEquals, common.ErrOperationBlocked) 2744 } else { 2745 c.Assert(err, jc.ErrorIsNil) 2746 c.Assert(len(machines), gc.Equals, 3) 2747 for i, machineResult := range machines { 2748 c.Assert(machineResult.Machine, gc.DeepEquals, strconv.Itoa(i)) 2749 s.checkMachine(c, machineResult.Machine, coretesting.FakeDefaultSeries, apiParams[i].Constraints.String()) 2750 } 2751 } 2752 } 2753 2754 func (s *clientSuite) TestBlockDestroyClientAddMachinesDefaultSeries(c *gc.C) { 2755 s.blockDestroyEnvironment(c) 2756 s.assertAddMachinesBlocked(c, false) 2757 } 2758 2759 func (s *clientSuite) TestBlockRemoveClientAddMachinesDefaultSeries(c *gc.C) { 2760 s.blockRemoveObject(c) 2761 s.assertAddMachinesBlocked(c, false) 2762 } 2763 2764 func (s *clientSuite) TestBlockChangesClientAddMachines(c *gc.C) { 2765 s.blockAllChanges(c) 2766 s.assertAddMachinesBlocked(c, true) 2767 } 2768 2769 func (s *clientSuite) TestClientAddMachinesWithSeries(c *gc.C) { 2770 apiParams := make([]params.AddMachineParams, 3) 2771 for i := 0; i < 3; i++ { 2772 apiParams[i] = params.AddMachineParams{ 2773 Series: "quantal", 2774 Jobs: []multiwatcher.MachineJob{multiwatcher.JobHostUnits}, 2775 } 2776 } 2777 machines, err := s.APIState.Client().AddMachines(apiParams) 2778 c.Assert(err, jc.ErrorIsNil) 2779 c.Assert(len(machines), gc.Equals, 3) 2780 for i, machineResult := range machines { 2781 c.Assert(machineResult.Machine, gc.DeepEquals, strconv.Itoa(i)) 2782 s.checkMachine(c, machineResult.Machine, "quantal", apiParams[i].Constraints.String()) 2783 } 2784 } 2785 2786 func (s *clientSuite) TestClientAddMachineInsideMachine(c *gc.C) { 2787 _, err := s.State.AddMachine("quantal", state.JobHostUnits) 2788 c.Assert(err, jc.ErrorIsNil) 2789 2790 machines, err := s.APIState.Client().AddMachines([]params.AddMachineParams{{ 2791 Jobs: []multiwatcher.MachineJob{multiwatcher.JobHostUnits}, 2792 ContainerType: instance.LXC, 2793 ParentId: "0", 2794 Series: "quantal", 2795 }}) 2796 c.Assert(err, jc.ErrorIsNil) 2797 c.Assert(machines, gc.HasLen, 1) 2798 c.Assert(machines[0].Machine, gc.Equals, "0/lxc/0") 2799 } 2800 2801 // updateConfig sets config variable with given key to a given value 2802 // Asserts that no errors were encountered. 2803 func (s *baseSuite) updateConfig(c *gc.C, key string, block bool) { 2804 err := s.State.UpdateEnvironConfig(map[string]interface{}{key: block}, nil, nil) 2805 c.Assert(err, jc.ErrorIsNil) 2806 } 2807 2808 // setBlockAllChanges blocks all operations that could change environment - 2809 // setting block-all-changes to true. 2810 func (s *baseSuite) setBlockAllChanges(c *gc.C, block bool) { 2811 s.updateConfig(c, "block-all-changes", block) 2812 } 2813 2814 func (s *baseSuite) blockAllChanges(c *gc.C) { 2815 s.setBlockAllChanges(c, true) 2816 } 2817 2818 // setBlockRemoveObject blocks all operations that remove 2819 // machines, services, units or relations - 2820 // setting block-remove-object to true. 2821 func (s *baseSuite) setBlockRemoveObject(c *gc.C, block bool) { 2822 s.updateConfig(c, "block-remove-object", block) 2823 } 2824 2825 func (s *baseSuite) blockRemoveObject(c *gc.C) { 2826 s.setBlockRemoveObject(c, true) 2827 } 2828 2829 // setBlockDestroyEnvironment blocks destroy-environment - 2830 // setting block-destroy-environment to true. 2831 func (s *baseSuite) setBlockDestroyEnvironment(c *gc.C, block bool) { 2832 s.updateConfig(c, "block-destroy-environment", block) 2833 } 2834 2835 func (s *baseSuite) blockDestroyEnvironment(c *gc.C) { 2836 s.setBlockDestroyEnvironment(c, true) 2837 } 2838 2839 func (s *clientSuite) TestClientAddMachinesWithConstraints(c *gc.C) { 2840 apiParams := make([]params.AddMachineParams, 3) 2841 for i := 0; i < 3; i++ { 2842 apiParams[i] = params.AddMachineParams{ 2843 Jobs: []multiwatcher.MachineJob{multiwatcher.JobHostUnits}, 2844 } 2845 } 2846 // The last machine has some constraints. 2847 apiParams[2].Constraints = constraints.MustParse("mem=4G") 2848 machines, err := s.APIState.Client().AddMachines(apiParams) 2849 c.Assert(err, jc.ErrorIsNil) 2850 c.Assert(len(machines), gc.Equals, 3) 2851 for i, machineResult := range machines { 2852 c.Assert(machineResult.Machine, gc.DeepEquals, strconv.Itoa(i)) 2853 s.checkMachine(c, machineResult.Machine, coretesting.FakeDefaultSeries, apiParams[i].Constraints.String()) 2854 } 2855 } 2856 2857 func (s *clientSuite) TestClientAddMachinesWithPlacement(c *gc.C) { 2858 apiParams := make([]params.AddMachineParams, 4) 2859 for i := range apiParams { 2860 apiParams[i] = params.AddMachineParams{ 2861 Jobs: []multiwatcher.MachineJob{multiwatcher.JobHostUnits}, 2862 } 2863 } 2864 apiParams[0].Placement = instance.MustParsePlacement("lxc") 2865 apiParams[1].Placement = instance.MustParsePlacement("lxc:0") 2866 apiParams[1].ContainerType = instance.LXC 2867 apiParams[2].Placement = instance.MustParsePlacement("dummyenv:invalid") 2868 apiParams[3].Placement = instance.MustParsePlacement("dummyenv:valid") 2869 machines, err := s.APIState.Client().AddMachines(apiParams) 2870 c.Assert(err, jc.ErrorIsNil) 2871 c.Assert(len(machines), gc.Equals, 4) 2872 c.Assert(machines[0].Machine, gc.Equals, "0/lxc/0") 2873 c.Assert(machines[1].Error, gc.ErrorMatches, "container type and placement are mutually exclusive") 2874 c.Assert(machines[2].Error, gc.ErrorMatches, "cannot add a new machine: invalid placement is invalid") 2875 c.Assert(machines[3].Machine, gc.Equals, "1") 2876 2877 m, err := s.BackingState.Machine(machines[3].Machine) 2878 c.Assert(err, jc.ErrorIsNil) 2879 c.Assert(m.Placement(), gc.DeepEquals, apiParams[3].Placement.Directive) 2880 } 2881 2882 func (s *clientSuite) TestClientAddMachinesWithDisks(c *gc.C) { 2883 s.PatchEnvironment(osenv.JujuFeatureFlagEnvKey, "storage") 2884 featureflag.SetFlagsFromEnvironment(osenv.JujuFeatureFlagEnvKey) 2885 2886 apiParams := make([]params.AddMachineParams, 3) 2887 for i := range apiParams { 2888 apiParams[i] = params.AddMachineParams{ 2889 Jobs: []multiwatcher.MachineJob{multiwatcher.JobHostUnits}, 2890 } 2891 } 2892 apiParams[0].Disks = []storage.Constraints{{Size: 1, Count: 2}, {Size: 2, Count: 1}} 2893 apiParams[1].Disks = []storage.Constraints{{Size: 1, Count: 2, Pool: "three"}} 2894 apiParams[2].Disks = []storage.Constraints{{Size: 0, Count: 0}} 2895 machines, err := s.APIState.Client().AddMachines(apiParams) 2896 c.Assert(err, jc.ErrorIsNil) 2897 c.Assert(len(machines), gc.Equals, 3) 2898 c.Assert(machines[0].Machine, gc.Equals, "0") 2899 c.Assert(machines[1].Error, gc.ErrorMatches, "storage pools not implemented") 2900 c.Assert(machines[2].Error, gc.ErrorMatches, "invalid size 0") 2901 2902 m, err := s.BackingState.Machine(machines[0].Machine) 2903 c.Assert(err, jc.ErrorIsNil) 2904 blockDevices, err := m.BlockDevices() 2905 c.Assert(err, jc.ErrorIsNil) 2906 expectParams := []state.BlockDeviceParams{{Size: 1}, {Size: 1}, {Size: 2}} 2907 c.Assert(blockDevices, gc.HasLen, len(expectParams)) 2908 for i, dev := range blockDevices { 2909 params, ok := dev.Params() 2910 c.Assert(ok, jc.IsTrue) 2911 c.Assert(params, gc.DeepEquals, expectParams[i]) 2912 } 2913 } 2914 2915 func (s *clientSuite) TestClientAddMachinesWithDisksNoFeatureFlag(c *gc.C) { 2916 // If the storage feature flag is not set, then Disks should be ignored. 2917 apiParams := make([]params.AddMachineParams, 2) 2918 for i := range apiParams { 2919 apiParams[i] = params.AddMachineParams{ 2920 Jobs: []multiwatcher.MachineJob{multiwatcher.JobHostUnits}, 2921 } 2922 } 2923 apiParams[0].Disks = []storage.Constraints{{Size: 1, Count: 2}, {Size: 2, Count: 1}} 2924 apiParams[1].Disks = []storage.Constraints{{Size: 1, Count: 0, Pool: "three"}} 2925 machines, err := s.APIState.Client().AddMachines(apiParams) 2926 c.Assert(err, jc.ErrorIsNil) 2927 c.Assert(len(machines), gc.Equals, 2) 2928 c.Assert(machines[0].Machine, gc.Equals, "0") 2929 c.Assert(machines[1].Machine, gc.Equals, "1") 2930 m, err := s.BackingState.Machine(machines[0].Machine) 2931 c.Assert(err, jc.ErrorIsNil) 2932 blockDevices, err := m.BlockDevices() 2933 c.Assert(err, jc.ErrorIsNil) 2934 c.Assert(blockDevices, gc.HasLen, 0) 2935 } 2936 2937 func (s *clientSuite) TestClientAddMachines1dot18(c *gc.C) { 2938 apiParams := make([]params.AddMachineParams, 2) 2939 for i := range apiParams { 2940 apiParams[i] = params.AddMachineParams{ 2941 Jobs: []multiwatcher.MachineJob{multiwatcher.JobHostUnits}, 2942 } 2943 } 2944 apiParams[1].ContainerType = instance.LXC 2945 apiParams[1].ParentId = "0" 2946 machines, err := s.APIState.Client().AddMachines1dot18(apiParams) 2947 c.Assert(err, jc.ErrorIsNil) 2948 c.Assert(len(machines), gc.Equals, 2) 2949 c.Assert(machines[0].Machine, gc.Equals, "0") 2950 c.Assert(machines[1].Machine, gc.Equals, "0/lxc/0") 2951 } 2952 2953 func (s *clientSuite) TestClientAddMachines1dot18SomeErrors(c *gc.C) { 2954 apiParams := []params.AddMachineParams{{ 2955 Jobs: []multiwatcher.MachineJob{multiwatcher.JobHostUnits}, 2956 ParentId: "123", 2957 }} 2958 machines, err := s.APIState.Client().AddMachines1dot18(apiParams) 2959 c.Assert(err, jc.ErrorIsNil) 2960 c.Assert(len(machines), gc.Equals, 1) 2961 c.Check(machines[0].Error, gc.ErrorMatches, "parent machine specified without container type") 2962 } 2963 2964 func (s *clientSuite) TestClientAddMachinesSomeErrors(c *gc.C) { 2965 // Here we check that adding a number of containers correctly handles the 2966 // case that some adds succeed and others fail and report the errors 2967 // accordingly. 2968 // We will set up params to the AddMachines API to attempt to create 3 machines. 2969 // Machines 0 and 1 will be added successfully. 2970 // Remaining machines will fail due to different reasons. 2971 2972 // Create a machine to host the requested containers. 2973 host, err := s.State.AddMachine("quantal", state.JobHostUnits) 2974 c.Assert(err, jc.ErrorIsNil) 2975 // The host only supports lxc containers. 2976 err = host.SetSupportedContainers([]instance.ContainerType{instance.LXC}) 2977 c.Assert(err, jc.ErrorIsNil) 2978 2979 // Set up params for adding 3 containers. 2980 apiParams := make([]params.AddMachineParams, 3) 2981 for i := range apiParams { 2982 apiParams[i] = params.AddMachineParams{ 2983 Jobs: []multiwatcher.MachineJob{multiwatcher.JobHostUnits}, 2984 } 2985 } 2986 // This will cause a machine add to fail due to an unsupported container. 2987 apiParams[2].ContainerType = instance.KVM 2988 apiParams[2].ParentId = host.Id() 2989 machines, err := s.APIState.Client().AddMachines(apiParams) 2990 c.Assert(err, jc.ErrorIsNil) 2991 c.Assert(len(machines), gc.Equals, 3) 2992 2993 // Check the results - machines 2 and 3 will have errors. 2994 c.Check(machines[0].Machine, gc.Equals, "1") 2995 c.Check(machines[0].Error, gc.IsNil) 2996 c.Check(machines[1].Machine, gc.Equals, "2") 2997 c.Check(machines[1].Error, gc.IsNil) 2998 c.Check(machines[2].Error, gc.ErrorMatches, "cannot add a new machine: machine 0 cannot host kvm containers") 2999 } 3000 3001 func (s *clientSuite) TestClientAddMachinesWithInstanceIdSomeErrors(c *gc.C) { 3002 apiParams := make([]params.AddMachineParams, 3) 3003 addrs := []network.Address{network.NewAddress("1.2.3.4", network.ScopeUnknown)} 3004 hc := instance.MustParseHardware("mem=4G") 3005 for i := 0; i < 3; i++ { 3006 apiParams[i] = params.AddMachineParams{ 3007 Jobs: []multiwatcher.MachineJob{multiwatcher.JobHostUnits}, 3008 InstanceId: instance.Id(fmt.Sprintf("1234-%d", i)), 3009 Nonce: "foo", 3010 HardwareCharacteristics: hc, 3011 Addrs: addrs, 3012 } 3013 } 3014 // This will cause the last machine add to fail. 3015 apiParams[2].Nonce = "" 3016 machines, err := s.APIState.Client().AddMachines(apiParams) 3017 c.Assert(err, jc.ErrorIsNil) 3018 c.Assert(len(machines), gc.Equals, 3) 3019 for i, machineResult := range machines { 3020 if i == 2 { 3021 c.Assert(machineResult.Error, gc.NotNil) 3022 c.Assert(machineResult.Error, gc.ErrorMatches, "cannot add a new machine: cannot add a machine with an instance id and no nonce") 3023 } else { 3024 c.Assert(machineResult.Machine, gc.DeepEquals, strconv.Itoa(i)) 3025 s.checkMachine(c, machineResult.Machine, coretesting.FakeDefaultSeries, apiParams[i].Constraints.String()) 3026 instanceId := fmt.Sprintf("1234-%d", i) 3027 s.checkInstance(c, machineResult.Machine, instanceId, "foo", hc, addrs) 3028 } 3029 } 3030 } 3031 3032 func (s *clientSuite) checkInstance(c *gc.C, id, instanceId, nonce string, 3033 hc instance.HardwareCharacteristics, addr []network.Address) { 3034 3035 machine, err := s.BackingState.Machine(id) 3036 c.Assert(err, jc.ErrorIsNil) 3037 machineInstanceId, err := machine.InstanceId() 3038 c.Assert(err, jc.ErrorIsNil) 3039 c.Assert(machine.CheckProvisioned(nonce), jc.IsTrue) 3040 c.Assert(machineInstanceId, gc.Equals, instance.Id(instanceId)) 3041 machineHardware, err := machine.HardwareCharacteristics() 3042 c.Assert(err, jc.ErrorIsNil) 3043 c.Assert(machineHardware.String(), gc.Equals, hc.String()) 3044 c.Assert(machine.Addresses(), gc.DeepEquals, addr) 3045 } 3046 3047 func (s *clientSuite) TestInjectMachinesStillExists(c *gc.C) { 3048 results := new(params.AddMachinesResults) 3049 // We need to use Call directly because the client interface 3050 // no longer refers to InjectMachine. 3051 args := params.AddMachines{ 3052 MachineParams: []params.AddMachineParams{{ 3053 Jobs: []multiwatcher.MachineJob{multiwatcher.JobHostUnits}, 3054 InstanceId: "i-foo", 3055 Nonce: "nonce", 3056 }}, 3057 } 3058 err := s.APIState.APICall("Client", 0, "", "AddMachines", args, &results) 3059 c.Assert(err, jc.ErrorIsNil) 3060 c.Assert(results.Machines, gc.HasLen, 1) 3061 } 3062 3063 func (s *clientSuite) TestProvisioningScript(c *gc.C) { 3064 // Inject a machine and then call the ProvisioningScript API. 3065 // The result should be the same as when calling MachineConfig, 3066 // converting it to a cloudinit.MachineConfig, and disabling 3067 // apt_upgrade. 3068 apiParams := params.AddMachineParams{ 3069 Jobs: []multiwatcher.MachineJob{multiwatcher.JobHostUnits}, 3070 InstanceId: instance.Id("1234"), 3071 Nonce: "foo", 3072 HardwareCharacteristics: instance.MustParseHardware("arch=amd64"), 3073 } 3074 machines, err := s.APIState.Client().AddMachines([]params.AddMachineParams{apiParams}) 3075 c.Assert(err, jc.ErrorIsNil) 3076 c.Assert(len(machines), gc.Equals, 1) 3077 machineId := machines[0].Machine 3078 // Call ProvisioningScript. Normally ProvisioningScript and 3079 // MachineConfig are mutually exclusive; both of them will 3080 // allocate a api password for the machine agent. 3081 script, err := s.APIState.Client().ProvisioningScript(params.ProvisioningScriptParams{ 3082 MachineId: machineId, 3083 Nonce: apiParams.Nonce, 3084 }) 3085 c.Assert(err, jc.ErrorIsNil) 3086 mcfg, err := client.MachineConfig(s.State, machineId, apiParams.Nonce, "") 3087 c.Assert(err, jc.ErrorIsNil) 3088 sshinitScript, err := manual.ProvisioningScript(mcfg) 3089 c.Assert(err, jc.ErrorIsNil) 3090 // ProvisioningScript internally calls MachineConfig, 3091 // which allocates a new, random password. Everything 3092 // about the scripts should be the same other than 3093 // the line containing "oldpassword" from agent.conf. 3094 scriptLines := strings.Split(script, "\n") 3095 sshinitScriptLines := strings.Split(sshinitScript, "\n") 3096 c.Assert(scriptLines, gc.HasLen, len(sshinitScriptLines)) 3097 for i, line := range scriptLines { 3098 if strings.Contains(line, "oldpassword") { 3099 continue 3100 } 3101 c.Assert(line, gc.Equals, sshinitScriptLines[i]) 3102 } 3103 } 3104 3105 func (s *clientSuite) TestProvisioningScriptDisablePackageCommands(c *gc.C) { 3106 apiParams := params.AddMachineParams{ 3107 Jobs: []multiwatcher.MachineJob{multiwatcher.JobHostUnits}, 3108 InstanceId: instance.Id("1234"), 3109 Nonce: "foo", 3110 HardwareCharacteristics: instance.MustParseHardware("arch=amd64"), 3111 } 3112 machines, err := s.APIState.Client().AddMachines([]params.AddMachineParams{apiParams}) 3113 c.Assert(err, jc.ErrorIsNil) 3114 c.Assert(len(machines), gc.Equals, 1) 3115 machineId := machines[0].Machine 3116 3117 provParams := params.ProvisioningScriptParams{ 3118 MachineId: machineId, 3119 Nonce: apiParams.Nonce, 3120 } 3121 3122 setUpdateBehavior := func(update, upgrade bool) { 3123 s.State.UpdateEnvironConfig( 3124 map[string]interface{}{ 3125 "enable-os-upgrade": upgrade, 3126 "enable-os-refresh-update": update, 3127 }, 3128 nil, 3129 nil, 3130 ) 3131 } 3132 3133 // Test enabling package commands 3134 provParams.DisablePackageCommands = false 3135 setUpdateBehavior(true, true) 3136 script, err := s.APIState.Client().ProvisioningScript(provParams) 3137 c.Assert(err, jc.ErrorIsNil) 3138 c.Check(script, jc.Contains, "apt-get update") 3139 c.Check(script, jc.Contains, "apt-get upgrade") 3140 3141 // Test disabling package commands 3142 provParams.DisablePackageCommands = true 3143 setUpdateBehavior(false, false) 3144 script, err = s.APIState.Client().ProvisioningScript(provParams) 3145 c.Assert(err, jc.ErrorIsNil) 3146 c.Check(script, gc.Not(jc.Contains), "apt-get update") 3147 c.Check(script, gc.Not(jc.Contains), "apt-get upgrade") 3148 3149 // Test client-specified DisablePackageCommands trumps environment 3150 // config variables. 3151 provParams.DisablePackageCommands = true 3152 setUpdateBehavior(true, true) 3153 script, err = s.APIState.Client().ProvisioningScript(provParams) 3154 c.Assert(err, jc.ErrorIsNil) 3155 c.Check(script, gc.Not(jc.Contains), "apt-get update") 3156 c.Check(script, gc.Not(jc.Contains), "apt-get upgrade") 3157 3158 // Test that in the abasence of a client-specified 3159 // DisablePackageCommands we use what's set in environments.yaml. 3160 provParams.DisablePackageCommands = false 3161 setUpdateBehavior(false, false) 3162 //provParams.UpdateBehavior = ¶ms.UpdateBehavior{false, false} 3163 script, err = s.APIState.Client().ProvisioningScript(provParams) 3164 c.Assert(err, jc.ErrorIsNil) 3165 c.Check(script, gc.Not(jc.Contains), "apt-get update") 3166 c.Check(script, gc.Not(jc.Contains), "apt-get upgrade") 3167 } 3168 3169 func (s *clientSuite) TestClientSpecializeStoreOnDeployServiceSetCharmAndAddCharm(c *gc.C) { 3170 store := s.makeMockCharmStore() 3171 3172 attrs := map[string]interface{}{"charm-store-auth": "token=value", 3173 "test-mode": true} 3174 err := s.State.UpdateEnvironConfig(attrs, nil, nil) 3175 c.Assert(err, jc.ErrorIsNil) 3176 3177 curl, _ := addCharm(c, "dummy") 3178 err = s.APIState.Client().ServiceDeploy( 3179 curl.String(), "service", 3, "", constraints.Value{}, "", 3180 ) 3181 c.Assert(err, jc.ErrorIsNil) 3182 3183 // check that the store's auth attributes were set 3184 c.Assert(store.AuthAttrs(), gc.Equals, "token=value") 3185 c.Assert(store.TestMode(), jc.IsTrue) 3186 3187 store.SetAuthAttrs("") 3188 3189 curl, _ = addCharm(c, "wordpress") 3190 err = s.APIState.Client().ServiceSetCharm( 3191 "service", curl.String(), false, 3192 ) 3193 3194 // check that the store's auth attributes were set 3195 c.Assert(store.AuthAttrs(), gc.Equals, "token=value") 3196 3197 curl, _ = addCharm(c, "riak") 3198 err = s.APIState.Client().AddCharm(curl) 3199 3200 // check that the store's auth attributes were set 3201 c.Assert(store.AuthAttrs(), gc.Equals, "token=value") 3202 } 3203 3204 func (s *clientSuite) TestAddCharm(c *gc.C) { 3205 s.makeMockCharmStore() 3206 3207 var blobs blobs 3208 s.PatchValue(client.NewStateStorage, func(uuid string, session *mgo.Session) statestorage.Storage { 3209 storage := statestorage.NewStorage(uuid, session) 3210 return &recordingStorage{Storage: storage, blobs: &blobs} 3211 }) 3212 3213 client := s.APIState.Client() 3214 // First test the sanity checks. 3215 err := client.AddCharm(&charm.URL{Name: "nonsense"}) 3216 c.Assert(err, gc.ErrorMatches, `charm URL has invalid schema: ":nonsense-0"`) 3217 err = client.AddCharm(charm.MustParseURL("local:precise/dummy")) 3218 c.Assert(err, gc.ErrorMatches, "only charm store charm URLs are supported, with cs: schema") 3219 err = client.AddCharm(charm.MustParseURL("cs:precise/wordpress")) 3220 c.Assert(err, gc.ErrorMatches, "charm URL must include revision") 3221 3222 // Add a charm, without uploading it to storage, to 3223 // check that AddCharm does not try to do it. 3224 charmDir := testcharms.Repo.CharmDir("dummy") 3225 ident := fmt.Sprintf("%s-%d", charmDir.Meta().Name, charmDir.Revision()) 3226 curl := charm.MustParseURL("cs:quantal/" + ident) 3227 sch, err := s.State.AddCharm(charmDir, curl, "", ident+"-sha256") 3228 c.Assert(err, jc.ErrorIsNil) 3229 3230 // AddCharm should see the charm in state and not upload it. 3231 err = client.AddCharm(sch.URL()) 3232 c.Assert(err, jc.ErrorIsNil) 3233 3234 c.Assert(blobs.m, gc.HasLen, 0) 3235 3236 // Now try adding another charm completely. 3237 curl, _ = addCharm(c, "wordpress") 3238 err = client.AddCharm(curl) 3239 c.Assert(err, jc.ErrorIsNil) 3240 3241 // Verify it's in state and it got uploaded. 3242 storage := statestorage.NewStorage(s.State.EnvironUUID(), s.State.MongoSession()) 3243 sch, err = s.State.Charm(curl) 3244 c.Assert(err, jc.ErrorIsNil) 3245 s.assertUploaded(c, storage, sch.StoragePath(), sch.BundleSha256()) 3246 } 3247 3248 var resolveCharmCases = []struct { 3249 schema, defaultSeries, charmName string 3250 parseErr string 3251 resolveErr string 3252 }{ 3253 {"cs", "precise", "wordpress", "", ""}, 3254 {"cs", "trusty", "wordpress", "", ""}, 3255 {"cs", "", "wordpress", "", `charm url series is not resolved`}, 3256 {"cs", "trusty", "", `charm URL has invalid charm name: "cs:"`, ""}, 3257 {"local", "trusty", "wordpress", "", `only charm store charm references are supported, with cs: schema`}, 3258 {"cs", "precise", "hl3", "", ""}, 3259 {"cs", "trusty", "hl3", "", ""}, 3260 {"cs", "", "hl3", "", `charm url series is not resolved`}, 3261 } 3262 3263 func (s *clientSuite) TestResolveCharm(c *gc.C) { 3264 store := s.makeMockCharmStore() 3265 3266 for i, test := range resolveCharmCases { 3267 c.Logf("test %d: %#v", i, test) 3268 // Mock charm store will use this to resolve a charm reference. 3269 store.SetDefaultSeries(test.defaultSeries) 3270 3271 client := s.APIState.Client() 3272 ref, err := charm.ParseReference(fmt.Sprintf("%s:%s", test.schema, test.charmName)) 3273 if test.parseErr == "" { 3274 if !c.Check(err, jc.ErrorIsNil) { 3275 continue 3276 } 3277 } else { 3278 c.Assert(err, gc.NotNil) 3279 c.Check(err, gc.ErrorMatches, test.parseErr) 3280 continue 3281 } 3282 c.Check(ref.String(), gc.Equals, fmt.Sprintf("%s:%s", test.schema, test.charmName)) 3283 3284 curl, err := client.ResolveCharm(ref) 3285 if err == nil { 3286 c.Assert(curl, gc.NotNil) 3287 // Only cs: schema should make it through here 3288 c.Check(curl.String(), gc.Equals, fmt.Sprintf("cs:%s/%s", test.defaultSeries, test.charmName)) 3289 c.Check(test.resolveErr, gc.Equals, "") 3290 } else { 3291 c.Check(curl, gc.IsNil) 3292 c.Check(err, gc.ErrorMatches, test.resolveErr) 3293 } 3294 } 3295 } 3296 3297 type blobs struct { 3298 sync.Mutex 3299 m map[string]bool // maps path to added (true), or deleted (false) 3300 } 3301 3302 // Add adds a path to the list of known paths. 3303 func (b *blobs) Add(path string) { 3304 b.Lock() 3305 defer b.Unlock() 3306 b.check() 3307 b.m[path] = true 3308 } 3309 3310 // Remove marks a path as deleted, even if it was not previously Added. 3311 func (b *blobs) Remove(path string) { 3312 b.Lock() 3313 defer b.Unlock() 3314 b.check() 3315 b.m[path] = false 3316 } 3317 3318 func (b *blobs) check() { 3319 if b.m == nil { 3320 b.m = make(map[string]bool) 3321 } 3322 } 3323 3324 type recordingStorage struct { 3325 statestorage.Storage 3326 putBarrier *sync.WaitGroup 3327 blobs *blobs 3328 } 3329 3330 func (s *recordingStorage) Put(path string, r io.Reader, size int64) error { 3331 if s.putBarrier != nil { 3332 // This goroutine has gotten to Put() so mark it Done() and 3333 // wait for the other goroutines to get to this point. 3334 s.putBarrier.Done() 3335 s.putBarrier.Wait() 3336 } 3337 if err := s.Storage.Put(path, r, size); err != nil { 3338 return errors.Trace(err) 3339 } 3340 s.blobs.Add(path) 3341 return nil 3342 } 3343 3344 func (s *recordingStorage) Remove(path string) error { 3345 if err := s.Storage.Remove(path); err != nil { 3346 return errors.Trace(err) 3347 } 3348 s.blobs.Remove(path) 3349 return nil 3350 } 3351 3352 func (s *clientSuite) TestAddCharmConcurrently(c *gc.C) { 3353 s.makeMockCharmStore() 3354 3355 var putBarrier sync.WaitGroup 3356 var blobs blobs 3357 s.PatchValue(client.NewStateStorage, func(uuid string, session *mgo.Session) statestorage.Storage { 3358 storage := statestorage.NewStorage(uuid, session) 3359 return &recordingStorage{Storage: storage, blobs: &blobs, putBarrier: &putBarrier} 3360 }) 3361 3362 client := s.APIState.Client() 3363 curl, _ := addCharm(c, "wordpress") 3364 3365 // Try adding the same charm concurrently from multiple goroutines 3366 // to test no "duplicate key errors" are reported (see lp bug 3367 // #1067979) and also at the end only one charm document is 3368 // created. 3369 3370 var wg sync.WaitGroup 3371 // We don't add them 1-by-1 because that would allow each goroutine to 3372 // finish separately without actually synchronizing between them 3373 putBarrier.Add(10) 3374 for i := 0; i < 10; i++ { 3375 wg.Add(1) 3376 go func(index int) { 3377 defer wg.Done() 3378 3379 c.Assert(client.AddCharm(curl), gc.IsNil, gc.Commentf("goroutine %d", index)) 3380 sch, err := s.State.Charm(curl) 3381 c.Assert(err, gc.IsNil, gc.Commentf("goroutine %d", index)) 3382 c.Assert(sch.URL(), jc.DeepEquals, curl, gc.Commentf("goroutine %d", index)) 3383 }(i) 3384 } 3385 wg.Wait() 3386 3387 blobs.Lock() 3388 3389 c.Assert(blobs.m, gc.HasLen, 10) 3390 3391 // Verify there is only a single uploaded charm remains and it 3392 // contains the correct data. 3393 sch, err := s.State.Charm(curl) 3394 c.Assert(err, jc.ErrorIsNil) 3395 storagePath := sch.StoragePath() 3396 c.Assert(blobs.m[storagePath], jc.IsTrue) 3397 for path, exists := range blobs.m { 3398 if path != storagePath { 3399 c.Assert(exists, jc.IsFalse) 3400 } 3401 } 3402 3403 storage := statestorage.NewStorage(s.State.EnvironUUID(), s.State.MongoSession()) 3404 s.assertUploaded(c, storage, sch.StoragePath(), sch.BundleSha256()) 3405 } 3406 3407 func (s *clientSuite) TestAddCharmOverwritesPlaceholders(c *gc.C) { 3408 s.makeMockCharmStore() 3409 3410 client := s.APIState.Client() 3411 curl, _ := addCharm(c, "wordpress") 3412 3413 // Add a placeholder with the same charm URL. 3414 err := s.State.AddStoreCharmPlaceholder(curl) 3415 c.Assert(err, jc.ErrorIsNil) 3416 _, err = s.State.Charm(curl) 3417 c.Assert(err, jc.Satisfies, errors.IsNotFound) 3418 3419 // Now try to add the charm, which will convert the placeholder to 3420 // a pending charm. 3421 err = client.AddCharm(curl) 3422 c.Assert(err, jc.ErrorIsNil) 3423 3424 // Make sure the document's flags were reset as expected. 3425 sch, err := s.State.Charm(curl) 3426 c.Assert(err, jc.ErrorIsNil) 3427 c.Assert(sch.URL(), jc.DeepEquals, curl) 3428 c.Assert(sch.IsPlaceholder(), jc.IsFalse) 3429 c.Assert(sch.IsUploaded(), jc.IsTrue) 3430 } 3431 3432 func (s *clientSuite) assertUploaded(c *gc.C, storage statestorage.Storage, storagePath, expectedSHA256 string) { 3433 reader, _, err := storage.Get(storagePath) 3434 c.Assert(err, jc.ErrorIsNil) 3435 defer reader.Close() 3436 downloadedSHA256, _, err := utils.ReadSHA256(reader) 3437 c.Assert(err, jc.ErrorIsNil) 3438 c.Assert(downloadedSHA256, gc.Equals, expectedSHA256) 3439 } 3440 3441 func (s *clientSuite) TestRetryProvisioning(c *gc.C) { 3442 machine, err := s.State.AddMachine("quantal", state.JobHostUnits) 3443 c.Assert(err, jc.ErrorIsNil) 3444 err = machine.SetStatus(state.StatusError, "error", nil) 3445 c.Assert(err, jc.ErrorIsNil) 3446 _, err = s.APIState.Client().RetryProvisioning(machine.Tag().(names.MachineTag)) 3447 c.Assert(err, jc.ErrorIsNil) 3448 3449 status, info, data, err := machine.Status() 3450 c.Assert(err, jc.ErrorIsNil) 3451 c.Assert(status, gc.Equals, state.StatusError) 3452 c.Assert(info, gc.Equals, "error") 3453 c.Assert(data["transient"], jc.IsTrue) 3454 } 3455 3456 func (s *clientSuite) setupRetryProvisioning(c *gc.C) *state.Machine { 3457 machine, err := s.State.AddMachine("quantal", state.JobHostUnits) 3458 c.Assert(err, jc.ErrorIsNil) 3459 err = machine.SetStatus(state.StatusError, "error", nil) 3460 c.Assert(err, jc.ErrorIsNil) 3461 return machine 3462 } 3463 3464 func (s *clientSuite) assertRetryProvisioningBlocked(c *gc.C, blocked bool, machine *state.Machine) { 3465 _, err := s.APIState.Client().RetryProvisioning(machine.Tag().(names.MachineTag)) 3466 if blocked { 3467 c.Assert(errors.Cause(err), gc.DeepEquals, common.ErrOperationBlocked) 3468 } else { 3469 c.Assert(err, jc.ErrorIsNil) 3470 status, info, data, err := machine.Status() 3471 c.Assert(err, jc.ErrorIsNil) 3472 c.Assert(status, gc.Equals, state.StatusError) 3473 c.Assert(info, gc.Equals, "error") 3474 c.Assert(data["transient"], jc.IsTrue) 3475 } 3476 } 3477 3478 func (s *clientSuite) TestBlockDestroyRetryProvisioning(c *gc.C) { 3479 m := s.setupRetryProvisioning(c) 3480 s.blockDestroyEnvironment(c) 3481 s.assertRetryProvisioningBlocked(c, false, m) 3482 } 3483 3484 func (s *clientSuite) TestBlockRemoveRetryProvisioning(c *gc.C) { 3485 m := s.setupRetryProvisioning(c) 3486 s.blockRemoveObject(c) 3487 s.assertRetryProvisioningBlocked(c, false, m) 3488 } 3489 3490 func (s *clientSuite) TestBlockChangesRetryProvisioning(c *gc.C) { 3491 m := s.setupRetryProvisioning(c) 3492 s.blockAllChanges(c) 3493 s.assertRetryProvisioningBlocked(c, true, m) 3494 } 3495 3496 func (s *clientSuite) TestAPIHostPorts(c *gc.C) { 3497 server1Addresses := []network.Address{{ 3498 Value: "server-1", 3499 Type: network.HostName, 3500 Scope: network.ScopePublic, 3501 }, { 3502 Value: "10.0.0.1", 3503 Type: network.IPv4Address, 3504 NetworkName: "internal", 3505 Scope: network.ScopeCloudLocal, 3506 }} 3507 server2Addresses := []network.Address{{ 3508 Value: "::1", 3509 Type: network.IPv6Address, 3510 NetworkName: "loopback", 3511 Scope: network.ScopeMachineLocal, 3512 }} 3513 stateAPIHostPorts := [][]network.HostPort{ 3514 network.AddressesWithPort(server1Addresses, 123), 3515 network.AddressesWithPort(server2Addresses, 456), 3516 } 3517 3518 err := s.State.SetAPIHostPorts(stateAPIHostPorts) 3519 c.Assert(err, jc.ErrorIsNil) 3520 apiHostPorts, err := s.APIState.Client().APIHostPorts() 3521 c.Assert(err, jc.ErrorIsNil) 3522 c.Assert(apiHostPorts, gc.DeepEquals, stateAPIHostPorts) 3523 } 3524 3525 func (s *clientSuite) TestClientAgentVersion(c *gc.C) { 3526 current := version.MustParse("1.2.0") 3527 s.PatchValue(&version.Current.Number, current) 3528 result, err := s.APIState.Client().AgentVersion() 3529 c.Assert(err, jc.ErrorIsNil) 3530 c.Assert(result, gc.Equals, current) 3531 } 3532 3533 func (s *clientSuite) TestMachineJobFromParams(c *gc.C) { 3534 var tests = []struct { 3535 name multiwatcher.MachineJob 3536 want state.MachineJob 3537 err string 3538 }{{ 3539 name: multiwatcher.JobHostUnits, 3540 want: state.JobHostUnits, 3541 }, { 3542 name: multiwatcher.JobManageEnviron, 3543 want: state.JobManageEnviron, 3544 }, { 3545 name: multiwatcher.JobManageNetworking, 3546 want: state.JobManageNetworking, 3547 }, { 3548 name: multiwatcher.JobManageStateDeprecated, 3549 want: state.JobManageStateDeprecated, 3550 }, { 3551 name: "invalid", 3552 want: -1, 3553 err: `invalid machine job "invalid"`, 3554 }} 3555 for _, test := range tests { 3556 got, err := client.MachineJobFromParams(test.name) 3557 if err != nil { 3558 c.Check(err, gc.ErrorMatches, test.err) 3559 } 3560 c.Check(got, gc.Equals, test.want) 3561 } 3562 } 3563 3564 func (s *serverSuite) TestBlockServiceDestroy(c *gc.C) { 3565 s.AddTestingService(c, "dummy-service", s.AddTestingCharm(c, "dummy")) 3566 // block remove-objects 3567 s.blockRemoveObject(c) 3568 3569 for i, t := range serviceDestroyTests { 3570 c.Logf("test %d. %s", i, t.about) 3571 err := s.APIState.Client().ServiceDestroy(t.service) 3572 c.Assert(errors.Cause(err), gc.ErrorMatches, common.ErrOperationBlocked.Error()) 3573 // Tests may have invalid service names. 3574 service, err := s.State.Service(t.service) 3575 if err == nil { 3576 // For valid service names, check that service is alive :-) 3577 assertLife(c, service, state.Alive) 3578 } 3579 } 3580 } 3581 3582 func (s *clientSuite) assertDestroyMachineSuccess(c *gc.C, u *state.Unit, m0, m1, m2 *state.Machine) { 3583 err := s.APIState.Client().DestroyMachines("0", "1", "2") 3584 c.Assert(err, gc.ErrorMatches, `some machines were not destroyed: machine 0 is required by the environment; machine 1 has unit "wordpress/0" assigned`) 3585 assertLife(c, m0, state.Alive) 3586 assertLife(c, m1, state.Alive) 3587 assertLife(c, m2, state.Dying) 3588 3589 err = u.UnassignFromMachine() 3590 c.Assert(err, jc.ErrorIsNil) 3591 err = s.APIState.Client().DestroyMachines("0", "1", "2") 3592 c.Assert(err, gc.ErrorMatches, `some machines were not destroyed: machine 0 is required by the environment`) 3593 assertLife(c, m0, state.Alive) 3594 assertLife(c, m1, state.Dying) 3595 assertLife(c, m2, state.Dying) 3596 } 3597 3598 func (s *clientSuite) assertBlockedErrorAndLiveliness(c *gc.C, err error, 3599 living1 state.Living, 3600 living2 state.Living, 3601 living3 state.Living, 3602 living4 state.Living) { 3603 c.Assert(errors.Cause(err), gc.ErrorMatches, common.ErrOperationBlocked.Error()) 3604 assertLife(c, living1, state.Alive) 3605 assertLife(c, living2, state.Alive) 3606 assertLife(c, living3, state.Alive) 3607 assertLife(c, living4, state.Alive) 3608 } 3609 3610 func (s *clientSuite) TestBlockRemoveDestroyMachines(c *gc.C) { 3611 m0, m1, m2, u := s.setupDestroyMachinesTest(c) 3612 s.blockRemoveObject(c) 3613 err := s.APIState.Client().DestroyMachines("0", "1", "2") 3614 s.assertBlockedErrorAndLiveliness(c, err, m0, m1, m2, u) 3615 } 3616 3617 func (s *clientSuite) TestBlockChangesDestroyMachines(c *gc.C) { 3618 m0, m1, m2, u := s.setupDestroyMachinesTest(c) 3619 s.blockAllChanges(c) 3620 err := s.APIState.Client().DestroyMachines("0", "1", "2") 3621 s.assertBlockedErrorAndLiveliness(c, err, m0, m1, m2, u) 3622 } 3623 3624 func (s *clientSuite) TestBlockDestoryDestroyMachines(c *gc.C) { 3625 m0, m1, m2, u := s.setupDestroyMachinesTest(c) 3626 s.blockDestroyEnvironment(c) 3627 s.assertDestroyMachineSuccess(c, u, m0, m1, m2) 3628 } 3629 3630 func (s *clientSuite) TestAnyBlockForceDestroyMachines(c *gc.C) { 3631 // force bypasses all blocks 3632 s.blockAllChanges(c) 3633 s.blockDestroyEnvironment(c) 3634 s.blockRemoveObject(c) 3635 s.assertForceDestroyMachines(c) 3636 } 3637 3638 func (s *clientSuite) assertForceDestroyMachines(c *gc.C) { 3639 m0, m1, m2, u := s.setupDestroyMachinesTest(c) 3640 3641 err := s.APIState.Client().ForceDestroyMachines("0", "1", "2") 3642 c.Assert(err, gc.ErrorMatches, `some machines were not destroyed: machine 0 is required by the environment`) 3643 assertLife(c, m0, state.Alive) 3644 assertLife(c, m1, state.Alive) 3645 assertLife(c, m2, state.Alive) 3646 assertLife(c, u, state.Alive) 3647 3648 err = s.State.Cleanup() 3649 c.Assert(err, jc.ErrorIsNil) 3650 assertLife(c, m0, state.Alive) 3651 assertLife(c, m1, state.Dead) 3652 assertLife(c, m2, state.Dead) 3653 assertRemoved(c, u) 3654 } 3655 3656 func (s *clientSuite) assertDestroyPrincipalUnits(c *gc.C, units []*state.Unit) { 3657 // Destroy 2 of them; check they become Dying. 3658 err := s.APIState.Client().DestroyServiceUnits("wordpress/0", "wordpress/1") 3659 c.Assert(err, jc.ErrorIsNil) 3660 assertLife(c, units[0], state.Dying) 3661 assertLife(c, units[1], state.Dying) 3662 3663 // Try to destroy an Alive one and a Dying one; check 3664 // it destroys the Alive one and ignores the Dying one. 3665 err = s.APIState.Client().DestroyServiceUnits("wordpress/2", "wordpress/0") 3666 c.Assert(err, jc.ErrorIsNil) 3667 assertLife(c, units[2], state.Dying) 3668 3669 // Try to destroy an Alive one along with a nonexistent one; check that 3670 // the valid instruction is followed but the invalid one is warned about. 3671 err = s.APIState.Client().DestroyServiceUnits("boojum/123", "wordpress/3") 3672 c.Assert(err, gc.ErrorMatches, `some units were not destroyed: unit "boojum/123" does not exist`) 3673 assertLife(c, units[3], state.Dying) 3674 3675 // Make one Dead, and destroy an Alive one alongside it; check no errors. 3676 wp0, err := s.State.Unit("wordpress/0") 3677 c.Assert(err, jc.ErrorIsNil) 3678 err = wp0.EnsureDead() 3679 c.Assert(err, jc.ErrorIsNil) 3680 err = s.APIState.Client().DestroyServiceUnits("wordpress/0", "wordpress/4") 3681 c.Assert(err, jc.ErrorIsNil) 3682 assertLife(c, units[0], state.Dead) 3683 assertLife(c, units[4], state.Dying) 3684 } 3685 3686 func (s *clientSuite) setupDestroyPrincipalUnits(c *gc.C) []*state.Unit { 3687 units := make([]*state.Unit, 5) 3688 wordpress := s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 3689 for i := range units { 3690 unit, err := wordpress.AddUnit() 3691 c.Assert(err, jc.ErrorIsNil) 3692 err = unit.SetStatus(state.StatusActive, "", nil) 3693 c.Assert(err, jc.ErrorIsNil) 3694 units[i] = unit 3695 } 3696 return units 3697 } 3698 func (s *clientSuite) TestBlockChangesDestroyPrincipalUnits(c *gc.C) { 3699 units := s.setupDestroyPrincipalUnits(c) 3700 s.blockAllChanges(c) 3701 err := s.APIState.Client().DestroyServiceUnits("wordpress/0", "wordpress/1") 3702 s.assertBlockedErrorAndLiveliness(c, err, units[0], units[1], units[2], units[3]) 3703 } 3704 3705 func (s *clientSuite) TestBlockRemoveDestroyPrincipalUnits(c *gc.C) { 3706 units := s.setupDestroyPrincipalUnits(c) 3707 s.blockRemoveObject(c) 3708 err := s.APIState.Client().DestroyServiceUnits("wordpress/0", "wordpress/1") 3709 s.assertBlockedErrorAndLiveliness(c, err, units[0], units[1], units[2], units[3]) 3710 } 3711 3712 func (s *clientSuite) TestBlockDestroyDestroyPrincipalUnits(c *gc.C) { 3713 units := s.setupDestroyPrincipalUnits(c) 3714 s.blockDestroyEnvironment(c) 3715 err := s.APIState.Client().DestroyServiceUnits("wordpress/0", "wordpress/1") 3716 c.Assert(err, jc.ErrorIsNil) 3717 assertLife(c, units[0], state.Dying) 3718 assertLife(c, units[1], state.Dying) 3719 } 3720 3721 func (s *clientSuite) assertDestroySubordinateUnits(c *gc.C, wordpress0, logging0 *state.Unit) { 3722 // Try to destroy the principal and the subordinate together; check it warns 3723 // about the subordinate, but destroys the one it can. (The principal unit 3724 // agent will be resposible for destroying the subordinate.) 3725 err := s.APIState.Client().DestroyServiceUnits("wordpress/0", "logging/0") 3726 c.Assert(err, gc.ErrorMatches, `some units were not destroyed: unit "logging/0" is a subordinate`) 3727 assertLife(c, wordpress0, state.Dying) 3728 assertLife(c, logging0, state.Alive) 3729 } 3730 3731 func (s *clientSuite) TestBlockRemoveDestroySubordinateUnits(c *gc.C) { 3732 wordpress := s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 3733 wordpress0, err := wordpress.AddUnit() 3734 c.Assert(err, jc.ErrorIsNil) 3735 s.AddTestingService(c, "logging", s.AddTestingCharm(c, "logging")) 3736 eps, err := s.State.InferEndpoints("logging", "wordpress") 3737 c.Assert(err, jc.ErrorIsNil) 3738 rel, err := s.State.AddRelation(eps...) 3739 c.Assert(err, jc.ErrorIsNil) 3740 ru, err := rel.Unit(wordpress0) 3741 c.Assert(err, jc.ErrorIsNil) 3742 err = ru.EnterScope(nil) 3743 c.Assert(err, jc.ErrorIsNil) 3744 logging0, err := s.State.Unit("logging/0") 3745 c.Assert(err, jc.ErrorIsNil) 3746 3747 s.blockRemoveObject(c) 3748 // Try to destroy the subordinate alone; check it fails. 3749 err = s.APIState.Client().DestroyServiceUnits("logging/0") 3750 c.Assert(errors.Cause(err), gc.ErrorMatches, common.ErrOperationBlocked.Error()) 3751 assertLife(c, rel, state.Alive) 3752 assertLife(c, wordpress0, state.Alive) 3753 assertLife(c, logging0, state.Alive) 3754 3755 err = s.APIState.Client().DestroyServiceUnits("wordpress/0", "logging/0") 3756 c.Assert(errors.Cause(err), gc.ErrorMatches, common.ErrOperationBlocked.Error()) 3757 assertLife(c, wordpress0, state.Alive) 3758 assertLife(c, logging0, state.Alive) 3759 assertLife(c, rel, state.Alive) 3760 } 3761 3762 func (s *clientSuite) TestBlockChangesDestroySubordinateUnits(c *gc.C) { 3763 wordpress := s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 3764 wordpress0, err := wordpress.AddUnit() 3765 c.Assert(err, jc.ErrorIsNil) 3766 s.AddTestingService(c, "logging", s.AddTestingCharm(c, "logging")) 3767 eps, err := s.State.InferEndpoints("logging", "wordpress") 3768 c.Assert(err, jc.ErrorIsNil) 3769 rel, err := s.State.AddRelation(eps...) 3770 c.Assert(err, jc.ErrorIsNil) 3771 ru, err := rel.Unit(wordpress0) 3772 c.Assert(err, jc.ErrorIsNil) 3773 err = ru.EnterScope(nil) 3774 c.Assert(err, jc.ErrorIsNil) 3775 logging0, err := s.State.Unit("logging/0") 3776 c.Assert(err, jc.ErrorIsNil) 3777 3778 s.blockAllChanges(c) 3779 // Try to destroy the subordinate alone; check it fails. 3780 err = s.APIState.Client().DestroyServiceUnits("logging/0") 3781 c.Assert(errors.Cause(err), gc.ErrorMatches, common.ErrOperationBlocked.Error()) 3782 assertLife(c, rel, state.Alive) 3783 assertLife(c, wordpress0, state.Alive) 3784 assertLife(c, logging0, state.Alive) 3785 3786 err = s.APIState.Client().DestroyServiceUnits("wordpress/0", "logging/0") 3787 c.Assert(errors.Cause(err), gc.ErrorMatches, common.ErrOperationBlocked.Error()) 3788 assertLife(c, wordpress0, state.Alive) 3789 assertLife(c, logging0, state.Alive) 3790 assertLife(c, rel, state.Alive) 3791 } 3792 3793 func (s *clientSuite) TestBlockDestroyDestroySubordinateUnits(c *gc.C) { 3794 wordpress := s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 3795 wordpress0, err := wordpress.AddUnit() 3796 c.Assert(err, jc.ErrorIsNil) 3797 s.AddTestingService(c, "logging", s.AddTestingCharm(c, "logging")) 3798 eps, err := s.State.InferEndpoints("logging", "wordpress") 3799 c.Assert(err, jc.ErrorIsNil) 3800 rel, err := s.State.AddRelation(eps...) 3801 c.Assert(err, jc.ErrorIsNil) 3802 ru, err := rel.Unit(wordpress0) 3803 c.Assert(err, jc.ErrorIsNil) 3804 err = ru.EnterScope(nil) 3805 c.Assert(err, jc.ErrorIsNil) 3806 logging0, err := s.State.Unit("logging/0") 3807 c.Assert(err, jc.ErrorIsNil) 3808 3809 s.blockDestroyEnvironment(c) 3810 // Try to destroy the subordinate alone; check it fails. 3811 err = s.APIState.Client().DestroyServiceUnits("logging/0") 3812 c.Assert(err, gc.ErrorMatches, `no units were destroyed: unit "logging/0" is a subordinate`) 3813 assertLife(c, logging0, state.Alive) 3814 3815 s.assertDestroySubordinateUnits(c, wordpress0, logging0) 3816 } 3817 3818 func (s *clientSuite) TestBlockRemoveDestroyRelation(c *gc.C) { 3819 endpoints := []string{"wordpress", "mysql"} 3820 relation := s.setupRelationScenario(c, endpoints) 3821 // block remove-objects 3822 s.blockRemoveObject(c) 3823 err := s.APIState.Client().DestroyRelation(endpoints...) 3824 c.Assert(errors.Cause(err), gc.ErrorMatches, common.ErrOperationBlocked.Error()) 3825 assertLife(c, relation, state.Alive) 3826 } 3827 3828 func (s *clientSuite) TestBlockChangeDestroyRelation(c *gc.C) { 3829 endpoints := []string{"wordpress", "mysql"} 3830 relation := s.setupRelationScenario(c, endpoints) 3831 s.blockAllChanges(c) 3832 err := s.APIState.Client().DestroyRelation(endpoints...) 3833 c.Assert(errors.Cause(err), gc.ErrorMatches, common.ErrOperationBlocked.Error()) 3834 assertLife(c, relation, state.Alive) 3835 } 3836 3837 func (s *clientSuite) TestBlockDestroyDestroyRelation(c *gc.C) { 3838 s.blockDestroyEnvironment(c) 3839 endpoints := []string{"wordpress", "mysql"} 3840 s.assertDestroyRelation(c, endpoints) 3841 }