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