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