github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/apiserver/application/application_test.go (about) 1 // Copyright 2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package application_test 5 6 import ( 7 "fmt" 8 "io" 9 "regexp" 10 "sync" 11 "time" 12 13 "github.com/juju/errors" 14 jc "github.com/juju/testing/checkers" 15 "github.com/juju/utils" 16 gc "gopkg.in/check.v1" 17 "gopkg.in/juju/charm.v6-unstable" 18 "gopkg.in/juju/charmrepo.v2-unstable" 19 "gopkg.in/juju/charmrepo.v2-unstable/csclient" 20 csparams "gopkg.in/juju/charmrepo.v2-unstable/csclient/params" 21 "gopkg.in/juju/names.v2" 22 "gopkg.in/macaroon.v1" 23 "gopkg.in/mgo.v2" 24 25 "github.com/juju/juju/apiserver/application" 26 "github.com/juju/juju/apiserver/common" 27 commontesting "github.com/juju/juju/apiserver/common/testing" 28 "github.com/juju/juju/apiserver/params" 29 apiservertesting "github.com/juju/juju/apiserver/testing" 30 "github.com/juju/juju/constraints" 31 "github.com/juju/juju/instance" 32 jujutesting "github.com/juju/juju/juju/testing" 33 "github.com/juju/juju/state" 34 statestorage "github.com/juju/juju/state/storage" 35 "github.com/juju/juju/status" 36 "github.com/juju/juju/storage" 37 "github.com/juju/juju/testcharms" 38 "github.com/juju/juju/testing/factory" 39 jujuversion "github.com/juju/juju/version" 40 ) 41 42 type serviceSuite struct { 43 jujutesting.JujuConnSuite 44 apiservertesting.CharmStoreSuite 45 commontesting.BlockHelper 46 47 applicationAPI *application.API 48 application *state.Application 49 authorizer apiservertesting.FakeAuthorizer 50 } 51 52 var _ = gc.Suite(&serviceSuite{}) 53 54 func (s *serviceSuite) SetUpSuite(c *gc.C) { 55 s.CharmStoreSuite.SetUpSuite(c) 56 s.JujuConnSuite.SetUpSuite(c) 57 } 58 59 func (s *serviceSuite) TearDownSuite(c *gc.C) { 60 s.CharmStoreSuite.TearDownSuite(c) 61 s.JujuConnSuite.TearDownSuite(c) 62 } 63 64 func (s *serviceSuite) SetUpTest(c *gc.C) { 65 s.JujuConnSuite.SetUpTest(c) 66 s.CharmStoreSuite.Session = s.JujuConnSuite.Session 67 s.CharmStoreSuite.SetUpTest(c) 68 s.BlockHelper = commontesting.NewBlockHelper(s.APIState) 69 s.AddCleanup(func(*gc.C) { s.BlockHelper.Close() }) 70 71 s.application = s.Factory.MakeApplication(c, nil) 72 73 s.authorizer = apiservertesting.FakeAuthorizer{ 74 Tag: s.AdminUserTag(c), 75 } 76 var err error 77 backend := application.NewStateBackend(s.State) 78 blockChecker := common.NewBlockChecker(s.State) 79 s.applicationAPI, err = application.NewAPI( 80 backend, s.authorizer, blockChecker, 81 application.CharmToStateCharm, 82 ) 83 c.Assert(err, jc.ErrorIsNil) 84 } 85 86 func (s *serviceSuite) TearDownTest(c *gc.C) { 87 s.CharmStoreSuite.TearDownTest(c) 88 s.JujuConnSuite.TearDownTest(c) 89 } 90 91 func (s *serviceSuite) TestSetMetricCredentials(c *gc.C) { 92 charm := s.Factory.MakeCharm(c, &factory.CharmParams{Name: "wordpress"}) 93 wordpress := s.Factory.MakeApplication(c, &factory.ApplicationParams{ 94 Charm: charm, 95 }) 96 tests := []struct { 97 about string 98 args params.ApplicationMetricCredentials 99 results params.ErrorResults 100 }{ 101 { 102 "test one argument and it passes", 103 params.ApplicationMetricCredentials{[]params.ApplicationMetricCredential{{ 104 s.application.Name(), 105 []byte("creds 1234"), 106 }}}, 107 params.ErrorResults{[]params.ErrorResult{{Error: nil}}}, 108 }, 109 { 110 "test two arguments and both pass", 111 params.ApplicationMetricCredentials{[]params.ApplicationMetricCredential{ 112 { 113 s.application.Name(), 114 []byte("creds 1234"), 115 }, 116 { 117 wordpress.Name(), 118 []byte("creds 4567"), 119 }, 120 }}, 121 params.ErrorResults{[]params.ErrorResult{ 122 {Error: nil}, 123 {Error: nil}, 124 }}, 125 }, 126 { 127 "test two arguments and second one fails", 128 params.ApplicationMetricCredentials{[]params.ApplicationMetricCredential{ 129 { 130 s.application.Name(), 131 []byte("creds 1234"), 132 }, 133 { 134 "not-a-application", 135 []byte("creds 4567"), 136 }, 137 }}, 138 params.ErrorResults{[]params.ErrorResult{ 139 {Error: nil}, 140 {Error: ¶ms.Error{Message: `application "not-a-application" not found`, Code: "not found"}}, 141 }}, 142 }, 143 } 144 for i, t := range tests { 145 c.Logf("Running test %d %v", i, t.about) 146 results, err := s.applicationAPI.SetMetricCredentials(t.args) 147 c.Assert(err, jc.ErrorIsNil) 148 c.Assert(results.Results, gc.HasLen, len(t.results.Results)) 149 c.Assert(results, gc.DeepEquals, t.results) 150 151 for i, a := range t.args.Creds { 152 if t.results.Results[i].Error == nil { 153 svc, err := s.State.Application(a.ApplicationName) 154 c.Assert(err, jc.ErrorIsNil) 155 creds := svc.MetricCredentials() 156 c.Assert(creds, gc.DeepEquals, a.MetricCredentials) 157 } 158 } 159 } 160 } 161 162 func (s *serviceSuite) TestCompatibleSettingsParsing(c *gc.C) { 163 // Test the exported settings parsing in a compatible way. 164 s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 165 svc, err := s.State.Application("dummy") 166 c.Assert(err, jc.ErrorIsNil) 167 ch, _, err := svc.Charm() 168 c.Assert(err, jc.ErrorIsNil) 169 c.Assert(ch.URL().String(), gc.Equals, "local:quantal/dummy-1") 170 171 // Empty string will be returned as nil. 172 options := map[string]string{ 173 "title": "foobar", 174 "username": "", 175 } 176 settings, err := application.ParseSettingsCompatible(ch.Config(), options) 177 c.Assert(err, jc.ErrorIsNil) 178 c.Assert(settings, gc.DeepEquals, charm.Settings{ 179 "title": "foobar", 180 "username": nil, 181 }) 182 183 // Illegal settings lead to an error. 184 options = map[string]string{ 185 "yummy": "didgeridoo", 186 } 187 _, err = application.ParseSettingsCompatible(ch.Config(), options) 188 c.Assert(err, gc.ErrorMatches, `unknown option "yummy"`) 189 } 190 191 func (s *serviceSuite) TestServiceDeployWithStorage(c *gc.C) { 192 curl, ch := s.UploadCharm(c, "utopic/storage-block-10", "storage-block") 193 err := application.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 194 URL: curl.String(), 195 }) 196 c.Assert(err, jc.ErrorIsNil) 197 storageConstraints := map[string]storage.Constraints{ 198 "data": { 199 Count: 1, 200 Size: 1024, 201 Pool: "environscoped-block", 202 }, 203 } 204 205 var cons constraints.Value 206 args := params.ApplicationDeploy{ 207 ApplicationName: "application", 208 CharmURL: curl.String(), 209 NumUnits: 1, 210 Constraints: cons, 211 Storage: storageConstraints, 212 } 213 results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{ 214 Applications: []params.ApplicationDeploy{args}}, 215 ) 216 c.Assert(err, jc.ErrorIsNil) 217 c.Assert(results, gc.DeepEquals, params.ErrorResults{ 218 Results: []params.ErrorResult{{Error: nil}}, 219 }) 220 svc := apiservertesting.AssertPrincipalServiceDeployed(c, s.State, "application", curl, false, ch, cons) 221 storageConstraintsOut, err := svc.StorageConstraints() 222 c.Assert(err, jc.ErrorIsNil) 223 c.Assert(storageConstraintsOut, gc.DeepEquals, map[string]state.StorageConstraints{ 224 "data": { 225 Count: 1, 226 Size: 1024, 227 Pool: "environscoped-block", 228 }, 229 "allecto": { 230 Count: 0, 231 Size: 1024, 232 Pool: "loop", 233 }, 234 }) 235 } 236 237 func (s *serviceSuite) TestMinJujuVersionTooHigh(c *gc.C) { 238 curl, _ := s.UploadCharm(c, "quantal/minjujuversion-0", "minjujuversion") 239 err := application.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 240 URL: curl.String(), 241 }) 242 match := fmt.Sprintf(`charm's min version (999.999.999) is higher than this juju environment's version (%s)`, jujuversion.Current) 243 c.Assert(err, gc.ErrorMatches, regexp.QuoteMeta(match)) 244 } 245 246 func (s *serviceSuite) TestServiceDeployWithInvalidStoragePool(c *gc.C) { 247 curl, _ := s.UploadCharm(c, "utopic/storage-block-0", "storage-block") 248 err := application.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 249 URL: curl.String(), 250 }) 251 c.Assert(err, jc.ErrorIsNil) 252 storageConstraints := map[string]storage.Constraints{ 253 "data": storage.Constraints{ 254 Pool: "foo", 255 Count: 1, 256 Size: 1024, 257 }, 258 } 259 260 var cons constraints.Value 261 args := params.ApplicationDeploy{ 262 ApplicationName: "application", 263 CharmURL: curl.String(), 264 NumUnits: 1, 265 Constraints: cons, 266 Storage: storageConstraints, 267 } 268 results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{ 269 Applications: []params.ApplicationDeploy{args}}, 270 ) 271 c.Assert(err, jc.ErrorIsNil) 272 c.Assert(results.Results, gc.HasLen, 1) 273 c.Assert(results.Results[0].Error, gc.ErrorMatches, `.* pool "foo" not found`) 274 } 275 276 func (s *serviceSuite) TestServiceDeployDefaultFilesystemStorage(c *gc.C) { 277 curl, ch := s.UploadCharm(c, "trusty/storage-filesystem-1", "storage-filesystem") 278 err := application.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 279 URL: curl.String(), 280 }) 281 c.Assert(err, jc.ErrorIsNil) 282 var cons constraints.Value 283 args := params.ApplicationDeploy{ 284 ApplicationName: "application", 285 CharmURL: curl.String(), 286 NumUnits: 1, 287 Constraints: cons, 288 } 289 results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{ 290 Applications: []params.ApplicationDeploy{args}}, 291 ) 292 c.Assert(err, jc.ErrorIsNil) 293 c.Assert(results, gc.DeepEquals, params.ErrorResults{ 294 Results: []params.ErrorResult{{Error: nil}}, 295 }) 296 svc := apiservertesting.AssertPrincipalServiceDeployed(c, s.State, "application", curl, false, ch, cons) 297 storageConstraintsOut, err := svc.StorageConstraints() 298 c.Assert(err, jc.ErrorIsNil) 299 c.Assert(storageConstraintsOut, gc.DeepEquals, map[string]state.StorageConstraints{ 300 "data": { 301 Count: 1, 302 Size: 1024, 303 Pool: "rootfs", 304 }, 305 }) 306 } 307 308 func (s *serviceSuite) TestServiceDeploy(c *gc.C) { 309 curl, ch := s.UploadCharm(c, "precise/dummy-42", "dummy") 310 err := application.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 311 URL: curl.String(), 312 }) 313 c.Assert(err, jc.ErrorIsNil) 314 var cons constraints.Value 315 args := params.ApplicationDeploy{ 316 ApplicationName: "application", 317 CharmURL: curl.String(), 318 NumUnits: 1, 319 Constraints: cons, 320 Placement: []*instance.Placement{ 321 {"deadbeef-0bad-400d-8000-4b1d0d06f00d", "valid"}, 322 }, 323 } 324 results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{ 325 Applications: []params.ApplicationDeploy{args}}, 326 ) 327 c.Assert(err, jc.ErrorIsNil) 328 c.Assert(results, gc.DeepEquals, params.ErrorResults{ 329 Results: []params.ErrorResult{{Error: nil}}, 330 }) 331 svc := apiservertesting.AssertPrincipalServiceDeployed(c, s.State, "application", curl, false, ch, cons) 332 units, err := svc.AllUnits() 333 c.Assert(err, jc.ErrorIsNil) 334 c.Assert(units, gc.HasLen, 1) 335 } 336 337 func (s *serviceSuite) TestServiceDeployWithInvalidPlacement(c *gc.C) { 338 curl, _ := s.UploadCharm(c, "precise/dummy-42", "dummy") 339 err := application.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 340 URL: curl.String(), 341 }) 342 c.Assert(err, jc.ErrorIsNil) 343 var cons constraints.Value 344 args := params.ApplicationDeploy{ 345 ApplicationName: "application", 346 CharmURL: curl.String(), 347 NumUnits: 1, 348 Constraints: cons, 349 Placement: []*instance.Placement{ 350 {"deadbeef-0bad-400d-8000-4b1d0d06f00d", "invalid"}, 351 }, 352 } 353 results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{ 354 Applications: []params.ApplicationDeploy{args}}, 355 ) 356 c.Assert(err, jc.ErrorIsNil) 357 c.Assert(results.Results, gc.HasLen, 1) 358 c.Assert(results.Results[0].Error, gc.NotNil) 359 c.Assert(results.Results[0].Error.Error(), gc.Matches, ".* invalid placement is invalid") 360 } 361 362 func (s *serviceSuite) testClientServicesDeployWithBindings(c *gc.C, endpointBindings, expected map[string]string) { 363 curl, _ := s.UploadCharm(c, "utopic/riak-42", "riak") 364 err := application.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 365 URL: curl.String(), 366 }) 367 c.Assert(err, jc.ErrorIsNil) 368 369 var cons constraints.Value 370 args := params.ApplicationDeploy{ 371 ApplicationName: "application", 372 CharmURL: curl.String(), 373 NumUnits: 1, 374 Constraints: cons, 375 EndpointBindings: endpointBindings, 376 } 377 378 results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{ 379 Applications: []params.ApplicationDeploy{args}}, 380 ) 381 c.Assert(err, jc.ErrorIsNil) 382 c.Assert(results.Results, gc.HasLen, 1) 383 c.Assert(results.Results[0].Error, gc.IsNil) 384 385 application, err := s.State.Application(args.ApplicationName) 386 c.Assert(err, jc.ErrorIsNil) 387 388 retrievedBindings, err := application.EndpointBindings() 389 c.Assert(err, jc.ErrorIsNil) 390 c.Assert(retrievedBindings, jc.DeepEquals, expected) 391 } 392 393 func (s *serviceSuite) TestClientServicesDeployWithBindings(c *gc.C) { 394 s.State.AddSpace("a-space", "", nil, true) 395 expected := map[string]string{ 396 "endpoint": "a-space", 397 "ring": "", 398 "admin": "", 399 } 400 endpointBindings := map[string]string{"endpoint": "a-space"} 401 s.testClientServicesDeployWithBindings(c, endpointBindings, expected) 402 } 403 404 func (s *serviceSuite) TestClientServicesDeployWithDefaultBindings(c *gc.C) { 405 expected := map[string]string{ 406 "endpoint": "", 407 "ring": "", 408 "admin": "", 409 } 410 s.testClientServicesDeployWithBindings(c, nil, expected) 411 } 412 413 // TODO(wallyworld) - the following charm tests have been moved from the apiserver/client 414 // package in order to use the fake charm store testing infrastructure. They are legacy tests 415 // written to use the api client instead of the apiserver logic. They need to be rewritten and 416 // feature tests added. 417 418 func (s *serviceSuite) TestAddCharm(c *gc.C) { 419 var blobs blobs 420 s.PatchValue(application.NewStateStorage, func(uuid string, session *mgo.Session) statestorage.Storage { 421 storage := statestorage.NewStorage(uuid, session) 422 return &recordingStorage{Storage: storage, blobs: &blobs} 423 }) 424 425 client := s.APIState.Client() 426 // First test the sanity checks. 427 err := client.AddCharm(&charm.URL{Name: "nonsense"}, csparams.StableChannel) 428 c.Assert(err, gc.ErrorMatches, `cannot parse charm or bundle URL: ":nonsense-0"`) 429 err = client.AddCharm(charm.MustParseURL("local:precise/dummy"), csparams.StableChannel) 430 c.Assert(err, gc.ErrorMatches, "only charm store charm URLs are supported, with cs: schema") 431 err = client.AddCharm(charm.MustParseURL("cs:precise/wordpress"), csparams.StableChannel) 432 c.Assert(err, gc.ErrorMatches, "charm URL must include revision") 433 434 // Add a charm, without uploading it to storage, to 435 // check that AddCharm does not try to do it. 436 charmDir := testcharms.Repo.CharmDir("dummy") 437 ident := fmt.Sprintf("%s-%d", charmDir.Meta().Name, charmDir.Revision()) 438 curl := charm.MustParseURL("cs:quantal/" + ident) 439 info := state.CharmInfo{ 440 Charm: charmDir, 441 ID: curl, 442 StoragePath: "", 443 SHA256: ident + "-sha256", 444 } 445 sch, err := s.State.AddCharm(info) 446 c.Assert(err, jc.ErrorIsNil) 447 448 // AddCharm should see the charm in state and not upload it. 449 err = client.AddCharm(sch.URL(), csparams.StableChannel) 450 c.Assert(err, jc.ErrorIsNil) 451 452 c.Assert(blobs.m, gc.HasLen, 0) 453 454 // Now try adding another charm completely. 455 curl, _ = s.UploadCharm(c, "precise/wordpress-3", "wordpress") 456 err = client.AddCharm(curl, csparams.StableChannel) 457 c.Assert(err, jc.ErrorIsNil) 458 459 // Verify it's in state and it got uploaded. 460 storage := statestorage.NewStorage(s.State.ModelUUID(), s.State.MongoSession()) 461 sch, err = s.State.Charm(curl) 462 c.Assert(err, jc.ErrorIsNil) 463 s.assertUploaded(c, storage, sch.StoragePath(), sch.BundleSha256()) 464 } 465 466 func (s *serviceSuite) TestAddCharmWithAuthorization(c *gc.C) { 467 // Upload a new charm to the charm store. 468 curl, _ := s.UploadCharm(c, "cs:~restricted/precise/wordpress-3", "wordpress") 469 470 // Change permissions on the new charm such that only bob 471 // can read from it. 472 s.DischargeUser = "restricted" 473 err := s.Client.Put("/"+curl.Path()+"/meta/perm/read", []string{"bob"}) 474 c.Assert(err, jc.ErrorIsNil) 475 476 // Try to add a charm to the environment without authorization. 477 s.DischargeUser = "" 478 err = s.APIState.Client().AddCharm(curl, csparams.StableChannel) 479 c.Assert(err, gc.ErrorMatches, `cannot retrieve charm "cs:~restricted/precise/wordpress-3": cannot get archive: cannot get discharge from "https://.*": third party refused discharge: cannot discharge: discharge denied \(unauthorized access\)`) 480 481 tryAs := func(user string) error { 482 client := csclient.New(csclient.Params{ 483 URL: s.Srv.URL, 484 }) 485 s.DischargeUser = user 486 var m *macaroon.Macaroon 487 err = client.Get("/delegatable-macaroon", &m) 488 c.Assert(err, gc.IsNil) 489 490 return application.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 491 URL: curl.String(), 492 Channel: string(csparams.StableChannel), 493 }) 494 } 495 // Try again with authorization for the wrong user. 496 err = tryAs("joe") 497 c.Assert(err, gc.ErrorMatches, `cannot retrieve charm "cs:~restricted/precise/wordpress-3": cannot get archive: unauthorized: access denied for user "joe"`) 498 499 // Try again with the correct authorization this time. 500 err = tryAs("bob") 501 c.Assert(err, gc.IsNil) 502 503 // Verify that it has actually been uploaded. 504 _, err = s.State.Charm(curl) 505 c.Assert(err, gc.IsNil) 506 } 507 508 func (s *serviceSuite) TestAddCharmConcurrently(c *gc.C) { 509 c.Skip("see lp:1596960 -- bad test for bad code") 510 511 var putBarrier sync.WaitGroup 512 var blobs blobs 513 s.PatchValue(application.NewStateStorage, func(uuid string, session *mgo.Session) statestorage.Storage { 514 storage := statestorage.NewStorage(uuid, session) 515 return &recordingStorage{Storage: storage, blobs: &blobs, putBarrier: &putBarrier} 516 }) 517 518 client := s.APIState.Client() 519 curl, _ := s.UploadCharm(c, "trusty/wordpress-3", "wordpress") 520 521 // Try adding the same charm concurrently from multiple goroutines 522 // to test no "duplicate key errors" are reported (see lp bug 523 // #1067979) and also at the end only one charm document is 524 // created. 525 526 var wg sync.WaitGroup 527 // We don't add them 1-by-1 because that would allow each goroutine to 528 // finish separately without actually synchronizing between them 529 putBarrier.Add(10) 530 for i := 0; i < 10; i++ { 531 wg.Add(1) 532 go func(index int) { 533 defer wg.Done() 534 535 c.Assert(client.AddCharm(curl, csparams.StableChannel), gc.IsNil, gc.Commentf("goroutine %d", index)) 536 sch, err := s.State.Charm(curl) 537 c.Assert(err, gc.IsNil, gc.Commentf("goroutine %d", index)) 538 c.Assert(sch.URL(), jc.DeepEquals, curl, gc.Commentf("goroutine %d", index)) 539 }(i) 540 } 541 wg.Wait() 542 543 blobs.Lock() 544 545 c.Assert(blobs.m, gc.HasLen, 10) 546 547 // Verify there is only a single uploaded charm remains and it 548 // contains the correct data. 549 sch, err := s.State.Charm(curl) 550 c.Assert(err, jc.ErrorIsNil) 551 storagePath := sch.StoragePath() 552 c.Assert(blobs.m[storagePath], jc.IsTrue) 553 for path, exists := range blobs.m { 554 if path != storagePath { 555 c.Assert(exists, jc.IsFalse) 556 } 557 } 558 559 storage := statestorage.NewStorage(s.State.ModelUUID(), s.State.MongoSession()) 560 s.assertUploaded(c, storage, sch.StoragePath(), sch.BundleSha256()) 561 } 562 563 func (s *serviceSuite) assertUploaded(c *gc.C, storage statestorage.Storage, storagePath, expectedSHA256 string) { 564 reader, _, err := storage.Get(storagePath) 565 c.Assert(err, jc.ErrorIsNil) 566 defer reader.Close() 567 downloadedSHA256, _, err := utils.ReadSHA256(reader) 568 c.Assert(err, jc.ErrorIsNil) 569 c.Assert(downloadedSHA256, gc.Equals, expectedSHA256) 570 } 571 572 func (s *serviceSuite) TestAddCharmOverwritesPlaceholders(c *gc.C) { 573 client := s.APIState.Client() 574 curl, _ := s.UploadCharm(c, "trusty/wordpress-42", "wordpress") 575 576 // Add a placeholder with the same charm URL. 577 err := s.State.AddStoreCharmPlaceholder(curl) 578 c.Assert(err, jc.ErrorIsNil) 579 _, err = s.State.Charm(curl) 580 c.Assert(err, jc.Satisfies, errors.IsNotFound) 581 582 // Now try to add the charm, which will convert the placeholder to 583 // a pending charm. 584 err = client.AddCharm(curl, csparams.StableChannel) 585 c.Assert(err, jc.ErrorIsNil) 586 587 // Make sure the document's flags were reset as expected. 588 sch, err := s.State.Charm(curl) 589 c.Assert(err, jc.ErrorIsNil) 590 c.Assert(sch.URL(), jc.DeepEquals, curl) 591 c.Assert(sch.IsPlaceholder(), jc.IsFalse) 592 c.Assert(sch.IsUploaded(), jc.IsTrue) 593 } 594 595 func (s *serviceSuite) TestServiceGetCharmURL(c *gc.C) { 596 s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 597 result, err := s.applicationAPI.GetCharmURL(params.ApplicationGet{"wordpress"}) 598 c.Assert(err, jc.ErrorIsNil) 599 c.Assert(result.Error, gc.IsNil) 600 c.Assert(result.Result, gc.Equals, "local:quantal/wordpress-3") 601 } 602 603 func (s *serviceSuite) TestServiceSetCharm(c *gc.C) { 604 curl, _ := s.UploadCharm(c, "precise/dummy-0", "dummy") 605 err := application.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 606 URL: curl.String(), 607 }) 608 c.Assert(err, jc.ErrorIsNil) 609 results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{ 610 Applications: []params.ApplicationDeploy{{ 611 CharmURL: curl.String(), 612 ApplicationName: "application", 613 NumUnits: 3, 614 }}}) 615 c.Assert(err, jc.ErrorIsNil) 616 c.Assert(results.Results, gc.HasLen, 1) 617 c.Assert(results.Results[0].Error, gc.IsNil) 618 curl, _ = s.UploadCharm(c, "precise/wordpress-3", "wordpress") 619 err = application.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 620 URL: curl.String(), 621 }) 622 c.Assert(err, jc.ErrorIsNil) 623 err = s.applicationAPI.SetCharm(params.ApplicationSetCharm{ 624 ApplicationName: "application", 625 CharmURL: curl.String(), 626 }) 627 c.Assert(err, jc.ErrorIsNil) 628 629 // Ensure that the charm is not marked as forced. 630 application, err := s.State.Application("application") 631 c.Assert(err, jc.ErrorIsNil) 632 charm, force, err := application.Charm() 633 c.Assert(err, jc.ErrorIsNil) 634 c.Assert(charm.URL().String(), gc.Equals, curl.String()) 635 c.Assert(force, jc.IsFalse) 636 } 637 638 func (s *serviceSuite) setupServiceSetCharm(c *gc.C) { 639 curl, _ := s.UploadCharm(c, "precise/dummy-0", "dummy") 640 err := application.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 641 URL: curl.String(), 642 }) 643 c.Assert(err, jc.ErrorIsNil) 644 results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{ 645 Applications: []params.ApplicationDeploy{{ 646 CharmURL: curl.String(), 647 ApplicationName: "application", 648 NumUnits: 3, 649 }}}) 650 c.Assert(err, jc.ErrorIsNil) 651 c.Assert(results.Results, gc.HasLen, 1) 652 c.Assert(results.Results[0].Error, gc.IsNil) 653 curl, _ = s.UploadCharm(c, "precise/wordpress-3", "wordpress") 654 err = application.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 655 URL: curl.String(), 656 }) 657 c.Assert(err, jc.ErrorIsNil) 658 } 659 660 func (s *serviceSuite) assertServiceSetCharm(c *gc.C, forceUnits bool) { 661 err := s.applicationAPI.SetCharm(params.ApplicationSetCharm{ 662 ApplicationName: "application", 663 CharmURL: "cs:~who/precise/wordpress-3", 664 ForceUnits: forceUnits, 665 }) 666 c.Assert(err, jc.ErrorIsNil) 667 // Ensure that the charm is not marked as forced. 668 application, err := s.State.Application("application") 669 c.Assert(err, jc.ErrorIsNil) 670 charm, _, err := application.Charm() 671 c.Assert(err, jc.ErrorIsNil) 672 c.Assert(charm.URL().String(), gc.Equals, "cs:~who/precise/wordpress-3") 673 } 674 675 func (s *serviceSuite) assertServiceSetCharmBlocked(c *gc.C, msg string) { 676 err := s.applicationAPI.SetCharm(params.ApplicationSetCharm{ 677 ApplicationName: "application", 678 CharmURL: "cs:~who/precise/wordpress-3", 679 }) 680 s.AssertBlocked(c, err, msg) 681 } 682 683 func (s *serviceSuite) TestBlockDestroyServiceSetCharm(c *gc.C) { 684 s.setupServiceSetCharm(c) 685 s.BlockDestroyModel(c, "TestBlockDestroyServiceSetCharm") 686 s.assertServiceSetCharm(c, false) 687 } 688 689 func (s *serviceSuite) TestBlockRemoveServiceSetCharm(c *gc.C) { 690 s.setupServiceSetCharm(c) 691 s.BlockRemoveObject(c, "TestBlockRemoveServiceSetCharm") 692 s.assertServiceSetCharm(c, false) 693 } 694 695 func (s *serviceSuite) TestBlockChangesServiceSetCharm(c *gc.C) { 696 s.setupServiceSetCharm(c) 697 s.BlockAllChanges(c, "TestBlockChangesServiceSetCharm") 698 s.assertServiceSetCharmBlocked(c, "TestBlockChangesServiceSetCharm") 699 } 700 701 func (s *serviceSuite) TestServiceSetCharmForceUnits(c *gc.C) { 702 curl, _ := s.UploadCharm(c, "precise/dummy-0", "dummy") 703 err := application.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 704 URL: curl.String(), 705 }) 706 c.Assert(err, jc.ErrorIsNil) 707 results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{ 708 Applications: []params.ApplicationDeploy{{ 709 CharmURL: curl.String(), 710 ApplicationName: "application", 711 NumUnits: 3, 712 }}}) 713 c.Assert(err, jc.ErrorIsNil) 714 c.Assert(results.Results, gc.HasLen, 1) 715 c.Assert(results.Results[0].Error, gc.IsNil) 716 curl, _ = s.UploadCharm(c, "precise/wordpress-3", "wordpress") 717 err = application.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 718 URL: curl.String(), 719 }) 720 c.Assert(err, jc.ErrorIsNil) 721 err = s.applicationAPI.SetCharm(params.ApplicationSetCharm{ 722 ApplicationName: "application", 723 CharmURL: curl.String(), 724 ForceUnits: true, 725 }) 726 c.Assert(err, jc.ErrorIsNil) 727 728 // Ensure that the charm is marked as forced. 729 application, err := s.State.Application("application") 730 c.Assert(err, jc.ErrorIsNil) 731 charm, force, err := application.Charm() 732 c.Assert(err, jc.ErrorIsNil) 733 c.Assert(charm.URL().String(), gc.Equals, curl.String()) 734 c.Assert(force, jc.IsTrue) 735 } 736 737 func (s *serviceSuite) TestBlockServiceSetCharmForce(c *gc.C) { 738 s.setupServiceSetCharm(c) 739 740 // block all changes 741 s.BlockAllChanges(c, "TestBlockServiceSetCharmForce") 742 s.BlockRemoveObject(c, "TestBlockServiceSetCharmForce") 743 s.BlockDestroyModel(c, "TestBlockServiceSetCharmForce") 744 745 s.assertServiceSetCharm(c, true) 746 } 747 748 func (s *serviceSuite) TestServiceSetCharmInvalidService(c *gc.C) { 749 err := s.applicationAPI.SetCharm(params.ApplicationSetCharm{ 750 ApplicationName: "badservice", 751 CharmURL: "cs:precise/wordpress-3", 752 ForceSeries: true, 753 ForceUnits: true, 754 }) 755 c.Assert(err, gc.ErrorMatches, `application "badservice" not found`) 756 } 757 758 func (s *serviceSuite) TestServiceAddCharmErrors(c *gc.C) { 759 for url, expect := range map[string]string{ 760 "wordpress": "charm URL must include revision", 761 "cs:wordpress": "charm URL must include revision", 762 "cs:precise/wordpress": "charm URL must include revision", 763 "cs:precise/wordpress-999999": `cannot retrieve "cs:precise/wordpress-999999": charm not found`, 764 } { 765 c.Logf("test %s", url) 766 err := application.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 767 URL: url, 768 }) 769 c.Check(err, gc.ErrorMatches, expect) 770 } 771 } 772 773 func (s *serviceSuite) TestServiceSetCharmLegacy(c *gc.C) { 774 curl, _ := s.UploadCharm(c, "precise/dummy-0", "dummy") 775 err := application.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 776 URL: curl.String(), 777 }) 778 c.Assert(err, jc.ErrorIsNil) 779 results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{ 780 Applications: []params.ApplicationDeploy{{ 781 CharmURL: curl.String(), 782 ApplicationName: "application", 783 }}}) 784 c.Assert(err, jc.ErrorIsNil) 785 c.Assert(results.Results, gc.HasLen, 1) 786 c.Assert(results.Results[0].Error, gc.IsNil) 787 curl, _ = s.UploadCharm(c, "trusty/dummy-1", "dummy") 788 err = application.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 789 URL: curl.String(), 790 }) 791 c.Assert(err, jc.ErrorIsNil) 792 793 // Even with forceSeries = true, we can't change a charm where 794 // the series is sepcified in the URL. 795 err = s.applicationAPI.SetCharm(params.ApplicationSetCharm{ 796 ApplicationName: "application", 797 CharmURL: curl.String(), 798 ForceSeries: true, 799 }) 800 c.Assert(err, gc.ErrorMatches, `cannot upgrade application "application" to charm "cs:~who/trusty/dummy-1": cannot change an application's series`) 801 } 802 803 func (s *serviceSuite) TestServiceSetCharmUnsupportedSeries(c *gc.C) { 804 curl, _ := s.UploadCharmMultiSeries(c, "~who/multi-series", "multi-series") 805 err := application.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 806 URL: curl.String(), 807 }) 808 c.Assert(err, jc.ErrorIsNil) 809 results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{ 810 Applications: []params.ApplicationDeploy{{ 811 CharmURL: curl.String(), 812 ApplicationName: "application", 813 Series: "precise", 814 }}}) 815 c.Assert(err, jc.ErrorIsNil) 816 c.Assert(results.Results, gc.HasLen, 1) 817 c.Assert(results.Results[0].Error, gc.IsNil) 818 curl, _ = s.UploadCharmMultiSeries(c, "~who/multi-series", "multi-series2") 819 err = application.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 820 URL: curl.String(), 821 }) 822 c.Assert(err, jc.ErrorIsNil) 823 824 err = s.applicationAPI.SetCharm(params.ApplicationSetCharm{ 825 ApplicationName: "application", 826 CharmURL: curl.String(), 827 }) 828 c.Assert(err, gc.ErrorMatches, `cannot upgrade application "application" to charm "cs:~who/multi-series-1": only these series are supported: trusty, wily`) 829 } 830 831 func (s *serviceSuite) assertServiceSetCharmSeries(c *gc.C, upgradeCharm, series string) { 832 curl, _ := s.UploadCharmMultiSeries(c, "~who/multi-series", "multi-series") 833 err := application.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 834 URL: curl.String(), 835 }) 836 c.Assert(err, jc.ErrorIsNil) 837 results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{ 838 Applications: []params.ApplicationDeploy{{ 839 CharmURL: curl.String(), 840 ApplicationName: "application", 841 Series: "precise", 842 }}}) 843 c.Assert(err, jc.ErrorIsNil) 844 c.Assert(results.Results, gc.HasLen, 1) 845 c.Assert(results.Results[0].Error, gc.IsNil) 846 847 url := upgradeCharm 848 if series != "" { 849 url = series + "/" + upgradeCharm 850 } 851 curl, _ = s.UploadCharmMultiSeries(c, "~who/"+url, upgradeCharm) 852 err = application.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 853 URL: curl.String(), 854 }) 855 c.Assert(err, jc.ErrorIsNil) 856 857 err = s.applicationAPI.SetCharm(params.ApplicationSetCharm{ 858 ApplicationName: "application", 859 CharmURL: curl.String(), 860 ForceSeries: true, 861 }) 862 c.Assert(err, jc.ErrorIsNil) 863 svc, err := s.State.Application("application") 864 c.Assert(err, jc.ErrorIsNil) 865 ch, _, err := svc.Charm() 866 c.Assert(err, jc.ErrorIsNil) 867 c.Assert(ch.URL().String(), gc.Equals, "cs:~who/"+url+"-0") 868 } 869 870 func (s *serviceSuite) TestServiceSetCharmUnsupportedSeriesForce(c *gc.C) { 871 s.assertServiceSetCharmSeries(c, "multi-series2", "") 872 } 873 874 func (s *serviceSuite) TestServiceSetCharmNoExplicitSupportedSeries(c *gc.C) { 875 s.assertServiceSetCharmSeries(c, "dummy", "precise") 876 } 877 878 func (s *serviceSuite) TestServiceSetCharmWrongOS(c *gc.C) { 879 curl, _ := s.UploadCharmMultiSeries(c, "~who/multi-series", "multi-series") 880 err := application.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 881 URL: curl.String(), 882 }) 883 c.Assert(err, jc.ErrorIsNil) 884 results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{ 885 Applications: []params.ApplicationDeploy{{ 886 CharmURL: curl.String(), 887 ApplicationName: "application", 888 Series: "precise", 889 }}}) 890 c.Assert(err, jc.ErrorIsNil) 891 c.Assert(results.Results, gc.HasLen, 1) 892 c.Assert(results.Results[0].Error, gc.IsNil) 893 curl, _ = s.UploadCharmMultiSeries(c, "~who/multi-series-windows", "multi-series-windows") 894 err = application.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 895 URL: curl.String(), 896 }) 897 c.Assert(err, jc.ErrorIsNil) 898 899 err = s.applicationAPI.SetCharm(params.ApplicationSetCharm{ 900 ApplicationName: "application", 901 CharmURL: curl.String(), 902 ForceSeries: true, 903 }) 904 c.Assert(err, gc.ErrorMatches, `cannot upgrade application "application" to charm "cs:~who/multi-series-windows-0": OS "Ubuntu" not supported by charm`) 905 } 906 907 type testModeCharmRepo struct { 908 *charmrepo.CharmStore 909 testMode bool 910 } 911 912 // WithTestMode returns a repository Interface where test mode is enabled. 913 func (s *testModeCharmRepo) WithTestMode() charmrepo.Interface { 914 s.testMode = true 915 return s.CharmStore.WithTestMode() 916 } 917 918 func (s *serviceSuite) TestSpecializeStoreOnDeployServiceSetCharmAndAddCharm(c *gc.C) { 919 repo := &testModeCharmRepo{} 920 s.PatchValue(&csclient.ServerURL, s.Srv.URL) 921 newCharmStoreRepo := application.NewCharmStoreRepo 922 s.PatchValue(&application.NewCharmStoreRepo, func(c *csclient.Client) charmrepo.Interface { 923 repo.CharmStore = newCharmStoreRepo(c).(*charmrepo.CharmStore) 924 return repo 925 }) 926 attrs := map[string]interface{}{"test-mode": true} 927 err := s.State.UpdateModelConfig(attrs, nil, nil) 928 c.Assert(err, jc.ErrorIsNil) 929 930 // Check that the store's test mode is enabled when calling service Deploy. 931 curl, _ := s.UploadCharm(c, "trusty/dummy-1", "dummy") 932 err = application.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 933 URL: curl.String(), 934 }) 935 c.Assert(err, jc.ErrorIsNil) 936 results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{ 937 Applications: []params.ApplicationDeploy{{ 938 CharmURL: curl.String(), 939 ApplicationName: "application", 940 NumUnits: 3, 941 }}}) 942 c.Assert(err, jc.ErrorIsNil) 943 c.Assert(results.Results, gc.HasLen, 1) 944 c.Assert(results.Results[0].Error, gc.IsNil) 945 c.Assert(repo.testMode, jc.IsTrue) 946 947 // Check that the store's test mode is enabled when calling SetCharm. 948 curl, _ = s.UploadCharm(c, "trusty/wordpress-2", "wordpress") 949 err = s.applicationAPI.SetCharm(params.ApplicationSetCharm{ 950 ApplicationName: "application", 951 CharmURL: curl.String(), 952 }) 953 c.Assert(repo.testMode, jc.IsTrue) 954 955 // Check that the store's test mode is enabled when calling AddCharm. 956 curl, _ = s.UploadCharm(c, "utopic/riak-42", "riak") 957 err = s.APIState.Client().AddCharm(curl, csparams.StableChannel) 958 c.Assert(err, jc.ErrorIsNil) 959 c.Assert(repo.testMode, jc.IsTrue) 960 } 961 962 func (s *serviceSuite) setupServiceDeploy(c *gc.C, args string) (*charm.URL, charm.Charm, constraints.Value) { 963 curl, ch := s.UploadCharm(c, "precise/dummy-42", "dummy") 964 err := application.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 965 URL: curl.String(), 966 }) 967 c.Assert(err, jc.ErrorIsNil) 968 cons := constraints.MustParse(args) 969 return curl, ch, cons 970 } 971 972 func (s *serviceSuite) assertServiceDeployPrincipal(c *gc.C, curl *charm.URL, ch charm.Charm, mem4g constraints.Value) { 973 results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{ 974 Applications: []params.ApplicationDeploy{{ 975 CharmURL: curl.String(), 976 ApplicationName: "application", 977 NumUnits: 3, 978 Constraints: mem4g, 979 }}}) 980 c.Assert(err, jc.ErrorIsNil) 981 c.Assert(results.Results, gc.HasLen, 1) 982 c.Assert(results.Results[0].Error, gc.IsNil) 983 apiservertesting.AssertPrincipalServiceDeployed(c, s.State, "application", curl, false, ch, mem4g) 984 } 985 986 func (s *serviceSuite) assertServiceDeployPrincipalBlocked(c *gc.C, msg string, curl *charm.URL, mem4g constraints.Value) { 987 _, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{ 988 Applications: []params.ApplicationDeploy{{ 989 CharmURL: curl.String(), 990 ApplicationName: "application", 991 NumUnits: 3, 992 Constraints: mem4g, 993 }}}) 994 s.AssertBlocked(c, err, msg) 995 } 996 997 func (s *serviceSuite) TestBlockDestroyServiceDeployPrincipal(c *gc.C) { 998 curl, bundle, cons := s.setupServiceDeploy(c, "mem=4G") 999 s.BlockDestroyModel(c, "TestBlockDestroyServiceDeployPrincipal") 1000 s.assertServiceDeployPrincipal(c, curl, bundle, cons) 1001 } 1002 1003 func (s *serviceSuite) TestBlockRemoveServiceDeployPrincipal(c *gc.C) { 1004 curl, bundle, cons := s.setupServiceDeploy(c, "mem=4G") 1005 s.BlockRemoveObject(c, "TestBlockRemoveServiceDeployPrincipal") 1006 s.assertServiceDeployPrincipal(c, curl, bundle, cons) 1007 } 1008 1009 func (s *serviceSuite) TestBlockChangesServiceDeployPrincipal(c *gc.C) { 1010 curl, _, cons := s.setupServiceDeploy(c, "mem=4G") 1011 s.BlockAllChanges(c, "TestBlockChangesServiceDeployPrincipal") 1012 s.assertServiceDeployPrincipalBlocked(c, "TestBlockChangesServiceDeployPrincipal", curl, cons) 1013 } 1014 1015 func (s *serviceSuite) TestServiceDeploySubordinate(c *gc.C) { 1016 curl, ch := s.UploadCharm(c, "utopic/logging-47", "logging") 1017 err := application.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 1018 URL: curl.String(), 1019 }) 1020 c.Assert(err, jc.ErrorIsNil) 1021 results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{ 1022 Applications: []params.ApplicationDeploy{{ 1023 CharmURL: curl.String(), 1024 ApplicationName: "application-name", 1025 }}}) 1026 c.Assert(err, jc.ErrorIsNil) 1027 c.Assert(results.Results, gc.HasLen, 1) 1028 c.Assert(results.Results[0].Error, gc.IsNil) 1029 1030 application, err := s.State.Application("application-name") 1031 c.Assert(err, jc.ErrorIsNil) 1032 charm, force, err := application.Charm() 1033 c.Assert(err, jc.ErrorIsNil) 1034 c.Assert(force, jc.IsFalse) 1035 c.Assert(charm.URL(), gc.DeepEquals, curl) 1036 c.Assert(charm.Meta(), gc.DeepEquals, ch.Meta()) 1037 c.Assert(charm.Config(), gc.DeepEquals, ch.Config()) 1038 1039 units, err := application.AllUnits() 1040 c.Assert(err, jc.ErrorIsNil) 1041 c.Assert(units, gc.HasLen, 0) 1042 } 1043 1044 func (s *serviceSuite) TestServiceDeployConfig(c *gc.C) { 1045 curl, _ := s.UploadCharm(c, "precise/dummy-0", "dummy") 1046 err := application.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 1047 URL: curl.String(), 1048 }) 1049 c.Assert(err, jc.ErrorIsNil) 1050 results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{ 1051 Applications: []params.ApplicationDeploy{{ 1052 CharmURL: curl.String(), 1053 ApplicationName: "application-name", 1054 NumUnits: 1, 1055 ConfigYAML: "application-name:\n username: fred", 1056 }}}) 1057 c.Assert(err, jc.ErrorIsNil) 1058 c.Assert(results.Results, gc.HasLen, 1) 1059 c.Assert(results.Results[0].Error, gc.IsNil) 1060 1061 application, err := s.State.Application("application-name") 1062 c.Assert(err, jc.ErrorIsNil) 1063 settings, err := application.ConfigSettings() 1064 c.Assert(err, jc.ErrorIsNil) 1065 c.Assert(settings, gc.DeepEquals, charm.Settings{"username": "fred"}) 1066 } 1067 1068 func (s *serviceSuite) TestServiceDeployConfigError(c *gc.C) { 1069 // TODO(fwereade): test Config/ConfigYAML handling directly on srvClient. 1070 // Can't be done cleanly until it's extracted similarly to Machiner. 1071 curl, _ := s.UploadCharm(c, "precise/dummy-0", "dummy") 1072 err := application.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 1073 URL: curl.String(), 1074 }) 1075 c.Assert(err, jc.ErrorIsNil) 1076 results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{ 1077 Applications: []params.ApplicationDeploy{{ 1078 CharmURL: curl.String(), 1079 ApplicationName: "application-name", 1080 NumUnits: 1, 1081 ConfigYAML: "application-name:\n skill-level: fred", 1082 }}}) 1083 c.Assert(err, jc.ErrorIsNil) 1084 c.Assert(results.Results, gc.HasLen, 1) 1085 c.Assert(results.Results[0].Error, gc.ErrorMatches, `option "skill-level" expected int, got "fred"`) 1086 _, err = s.State.Application("application-name") 1087 c.Assert(err, jc.Satisfies, errors.IsNotFound) 1088 } 1089 1090 func (s *serviceSuite) TestServiceDeployToMachine(c *gc.C) { 1091 curl, ch := s.UploadCharm(c, "precise/dummy-0", "dummy") 1092 err := application.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 1093 URL: curl.String(), 1094 }) 1095 c.Assert(err, jc.ErrorIsNil) 1096 1097 machine, err := s.State.AddMachine("precise", state.JobHostUnits) 1098 c.Assert(err, jc.ErrorIsNil) 1099 results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{ 1100 Applications: []params.ApplicationDeploy{{ 1101 CharmURL: curl.String(), 1102 ApplicationName: "application-name", 1103 NumUnits: 1, 1104 ConfigYAML: "application-name:\n username: fred", 1105 }}}) 1106 c.Assert(err, jc.ErrorIsNil) 1107 c.Assert(results.Results, gc.HasLen, 1) 1108 c.Assert(results.Results[0].Error, gc.IsNil) 1109 1110 application, err := s.State.Application("application-name") 1111 c.Assert(err, jc.ErrorIsNil) 1112 charm, force, err := application.Charm() 1113 c.Assert(err, jc.ErrorIsNil) 1114 c.Assert(force, jc.IsFalse) 1115 c.Assert(charm.URL(), gc.DeepEquals, curl) 1116 c.Assert(charm.Meta(), gc.DeepEquals, ch.Meta()) 1117 c.Assert(charm.Config(), gc.DeepEquals, ch.Config()) 1118 1119 errs, err := s.APIState.UnitAssigner().AssignUnits([]names.UnitTag{names.NewUnitTag("application-name/0")}) 1120 c.Assert(errs, gc.DeepEquals, []error{nil}) 1121 c.Assert(err, jc.ErrorIsNil) 1122 1123 units, err := application.AllUnits() 1124 c.Assert(err, jc.ErrorIsNil) 1125 c.Assert(units, gc.HasLen, 1) 1126 1127 mid, err := units[0].AssignedMachineId() 1128 c.Assert(err, jc.ErrorIsNil) 1129 c.Assert(mid, gc.Equals, machine.Id()) 1130 } 1131 1132 func (s *serviceSuite) TestServiceDeployToMachineNotFound(c *gc.C) { 1133 results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{ 1134 Applications: []params.ApplicationDeploy{{ 1135 CharmURL: "cs:precise/application-name-1", 1136 ApplicationName: "application-name", 1137 NumUnits: 1, 1138 Placement: []*instance.Placement{instance.MustParsePlacement("42")}, 1139 }}}) 1140 c.Assert(err, jc.ErrorIsNil) 1141 c.Assert(results.Results, gc.HasLen, 1) 1142 c.Assert(results.Results[0].Error, gc.ErrorMatches, `cannot deploy "application-name" to machine 42: machine 42 not found`) 1143 1144 _, err = s.State.Application("application-name") 1145 c.Assert(err, gc.ErrorMatches, `application "application-name" not found`) 1146 } 1147 1148 func (s *serviceSuite) deployServiceForUpdateTests(c *gc.C) { 1149 curl, _ := s.UploadCharm(c, "precise/dummy-1", "dummy") 1150 err := application.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 1151 URL: curl.String(), 1152 }) 1153 c.Assert(err, jc.ErrorIsNil) 1154 results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{ 1155 Applications: []params.ApplicationDeploy{{ 1156 CharmURL: curl.String(), 1157 ApplicationName: "application", 1158 NumUnits: 1, 1159 }}}) 1160 c.Assert(err, jc.ErrorIsNil) 1161 c.Assert(results.Results, gc.HasLen, 1) 1162 c.Assert(results.Results[0].Error, gc.IsNil) 1163 } 1164 1165 func (s *serviceSuite) checkClientServiceUpdateSetCharm(c *gc.C, forceCharmURL bool) { 1166 s.deployServiceForUpdateTests(c) 1167 curl, _ := s.UploadCharm(c, "precise/wordpress-3", "wordpress") 1168 err := application.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 1169 URL: curl.String(), 1170 }) 1171 c.Assert(err, jc.ErrorIsNil) 1172 1173 // Update the charm for the application. 1174 args := params.ApplicationUpdate{ 1175 ApplicationName: "application", 1176 CharmURL: curl.String(), 1177 ForceCharmURL: forceCharmURL, 1178 } 1179 err = s.applicationAPI.Update(args) 1180 c.Assert(err, jc.ErrorIsNil) 1181 1182 // Ensure the charm has been updated and and the force flag correctly set. 1183 application, err := s.State.Application("application") 1184 c.Assert(err, jc.ErrorIsNil) 1185 ch, force, err := application.Charm() 1186 c.Assert(err, jc.ErrorIsNil) 1187 c.Assert(ch.URL().String(), gc.Equals, curl.String()) 1188 c.Assert(force, gc.Equals, forceCharmURL) 1189 } 1190 1191 func (s *serviceSuite) TestServiceUpdateSetCharm(c *gc.C) { 1192 s.checkClientServiceUpdateSetCharm(c, false) 1193 } 1194 1195 func (s *serviceSuite) TestBlockDestroyServiceUpdate(c *gc.C) { 1196 s.BlockDestroyModel(c, "TestBlockDestroyServiceUpdate") 1197 s.checkClientServiceUpdateSetCharm(c, false) 1198 } 1199 1200 func (s *serviceSuite) TestBlockRemoveServiceUpdate(c *gc.C) { 1201 s.BlockRemoveObject(c, "TestBlockRemoveServiceUpdate") 1202 s.checkClientServiceUpdateSetCharm(c, false) 1203 } 1204 1205 func (s *serviceSuite) setupServiceUpdate(c *gc.C) string { 1206 s.deployServiceForUpdateTests(c) 1207 curl, _ := s.UploadCharm(c, "precise/wordpress-3", "wordpress") 1208 err := application.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 1209 URL: curl.String(), 1210 }) 1211 c.Assert(err, jc.ErrorIsNil) 1212 return curl.String() 1213 } 1214 1215 func (s *serviceSuite) TestBlockChangeServiceUpdate(c *gc.C) { 1216 curl := s.setupServiceUpdate(c) 1217 s.BlockAllChanges(c, "TestBlockChangeServiceUpdate") 1218 // Update the charm for the application. 1219 args := params.ApplicationUpdate{ 1220 ApplicationName: "application", 1221 CharmURL: curl, 1222 ForceCharmURL: false, 1223 } 1224 err := s.applicationAPI.Update(args) 1225 s.AssertBlocked(c, err, "TestBlockChangeServiceUpdate") 1226 } 1227 1228 func (s *serviceSuite) TestServiceUpdateForceSetCharm(c *gc.C) { 1229 s.checkClientServiceUpdateSetCharm(c, true) 1230 } 1231 1232 func (s *serviceSuite) TestBlockServiceUpdateForced(c *gc.C) { 1233 curl := s.setupServiceUpdate(c) 1234 1235 // block all changes. Force should ignore block :) 1236 s.BlockAllChanges(c, "TestBlockServiceUpdateForced") 1237 s.BlockDestroyModel(c, "TestBlockServiceUpdateForced") 1238 s.BlockRemoveObject(c, "TestBlockServiceUpdateForced") 1239 1240 // Update the charm for the application. 1241 args := params.ApplicationUpdate{ 1242 ApplicationName: "application", 1243 CharmURL: curl, 1244 ForceCharmURL: true, 1245 } 1246 err := s.applicationAPI.Update(args) 1247 c.Assert(err, jc.ErrorIsNil) 1248 1249 // Ensure the charm has been updated and and the force flag correctly set. 1250 application, err := s.State.Application("application") 1251 c.Assert(err, jc.ErrorIsNil) 1252 ch, force, err := application.Charm() 1253 c.Assert(err, jc.ErrorIsNil) 1254 c.Assert(ch.URL().String(), gc.Equals, curl) 1255 c.Assert(force, jc.IsTrue) 1256 } 1257 1258 func (s *serviceSuite) TestServiceUpdateSetCharmNotFound(c *gc.C) { 1259 s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 1260 args := params.ApplicationUpdate{ 1261 ApplicationName: "wordpress", 1262 CharmURL: "cs:precise/wordpress-999999", 1263 } 1264 err := s.applicationAPI.Update(args) 1265 c.Check(err, gc.ErrorMatches, `charm "cs:precise/wordpress-999999" not found`) 1266 } 1267 1268 func (s *serviceSuite) TestServiceUpdateSetMinUnits(c *gc.C) { 1269 application := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 1270 1271 // Set minimum units for the application. 1272 minUnits := 2 1273 args := params.ApplicationUpdate{ 1274 ApplicationName: "dummy", 1275 MinUnits: &minUnits, 1276 } 1277 err := s.applicationAPI.Update(args) 1278 c.Assert(err, jc.ErrorIsNil) 1279 1280 // Ensure the minimum number of units has been set. 1281 c.Assert(application.Refresh(), gc.IsNil) 1282 c.Assert(application.MinUnits(), gc.Equals, minUnits) 1283 } 1284 1285 func (s *serviceSuite) TestServiceUpdateSetMinUnitsError(c *gc.C) { 1286 application := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 1287 1288 // Set a negative minimum number of units for the application. 1289 minUnits := -1 1290 args := params.ApplicationUpdate{ 1291 ApplicationName: "dummy", 1292 MinUnits: &minUnits, 1293 } 1294 err := s.applicationAPI.Update(args) 1295 c.Assert(err, gc.ErrorMatches, 1296 `cannot set minimum units for application "dummy": cannot set a negative minimum number of units`) 1297 1298 // Ensure the minimum number of units has not been set. 1299 c.Assert(application.Refresh(), gc.IsNil) 1300 c.Assert(application.MinUnits(), gc.Equals, 0) 1301 } 1302 1303 func (s *serviceSuite) TestServiceUpdateSetSettingsStrings(c *gc.C) { 1304 application := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 1305 1306 // Update settings for the application. 1307 args := params.ApplicationUpdate{ 1308 ApplicationName: "dummy", 1309 SettingsStrings: map[string]string{"title": "s-title", "username": "s-user"}, 1310 } 1311 err := s.applicationAPI.Update(args) 1312 c.Assert(err, jc.ErrorIsNil) 1313 1314 // Ensure the settings have been correctly updated. 1315 expected := charm.Settings{"title": "s-title", "username": "s-user"} 1316 obtained, err := application.ConfigSettings() 1317 c.Assert(err, jc.ErrorIsNil) 1318 c.Assert(obtained, gc.DeepEquals, expected) 1319 } 1320 1321 func (s *serviceSuite) TestServiceUpdateSetSettingsYAML(c *gc.C) { 1322 application := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 1323 1324 // Update settings for the application. 1325 args := params.ApplicationUpdate{ 1326 ApplicationName: "dummy", 1327 SettingsYAML: "dummy:\n title: y-title\n username: y-user", 1328 } 1329 err := s.applicationAPI.Update(args) 1330 c.Assert(err, jc.ErrorIsNil) 1331 1332 // Ensure the settings have been correctly updated. 1333 expected := charm.Settings{"title": "y-title", "username": "y-user"} 1334 obtained, err := application.ConfigSettings() 1335 c.Assert(err, jc.ErrorIsNil) 1336 c.Assert(obtained, gc.DeepEquals, expected) 1337 } 1338 1339 func (s *serviceSuite) TestClientServiceUpdateSetSettingsGetYAML(c *gc.C) { 1340 application := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 1341 1342 // Update settings for the application. 1343 args := params.ApplicationUpdate{ 1344 ApplicationName: "dummy", 1345 SettingsYAML: "charm: dummy\napplication: dummy\nsettings:\n title:\n value: y-title\n type: string\n username:\n value: y-user\n ignore:\n blah: true", 1346 } 1347 err := s.applicationAPI.Update(args) 1348 c.Assert(err, jc.ErrorIsNil) 1349 1350 // Ensure the settings have been correctly updated. 1351 expected := charm.Settings{"title": "y-title", "username": "y-user"} 1352 obtained, err := application.ConfigSettings() 1353 c.Assert(err, jc.ErrorIsNil) 1354 c.Assert(obtained, gc.DeepEquals, expected) 1355 } 1356 1357 func (s *serviceSuite) TestServiceUpdateSetConstraints(c *gc.C) { 1358 application := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 1359 1360 // Update constraints for the application. 1361 cons, err := constraints.Parse("mem=4096", "cores=2") 1362 c.Assert(err, jc.ErrorIsNil) 1363 args := params.ApplicationUpdate{ 1364 ApplicationName: "dummy", 1365 Constraints: &cons, 1366 } 1367 err = s.applicationAPI.Update(args) 1368 c.Assert(err, jc.ErrorIsNil) 1369 1370 // Ensure the constraints have been correctly updated. 1371 obtained, err := application.Constraints() 1372 c.Assert(err, jc.ErrorIsNil) 1373 c.Assert(obtained, gc.DeepEquals, cons) 1374 } 1375 1376 func (s *serviceSuite) TestServiceUpdateAllParams(c *gc.C) { 1377 s.deployServiceForUpdateTests(c) 1378 curl, _ := s.UploadCharm(c, "precise/wordpress-3", "wordpress") 1379 err := application.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 1380 URL: curl.String(), 1381 }) 1382 c.Assert(err, jc.ErrorIsNil) 1383 1384 // Update all the service attributes. 1385 minUnits := 3 1386 cons, err := constraints.Parse("mem=4096", "cores=2") 1387 c.Assert(err, jc.ErrorIsNil) 1388 args := params.ApplicationUpdate{ 1389 ApplicationName: "application", 1390 CharmURL: curl.String(), 1391 ForceCharmURL: true, 1392 MinUnits: &minUnits, 1393 SettingsStrings: map[string]string{"blog-title": "string-title"}, 1394 SettingsYAML: "application:\n blog-title: yaml-title\n", 1395 Constraints: &cons, 1396 } 1397 err = s.applicationAPI.Update(args) 1398 c.Assert(err, jc.ErrorIsNil) 1399 1400 // Ensure the service has been correctly updated. 1401 application, err := s.State.Application("application") 1402 c.Assert(err, jc.ErrorIsNil) 1403 1404 // Check the charm. 1405 ch, force, err := application.Charm() 1406 c.Assert(err, jc.ErrorIsNil) 1407 c.Assert(ch.URL().String(), gc.Equals, curl.String()) 1408 c.Assert(force, jc.IsTrue) 1409 1410 // Check the minimum number of units. 1411 c.Assert(application.MinUnits(), gc.Equals, minUnits) 1412 1413 // Check the settings: also ensure the YAML settings take precedence 1414 // over strings ones. 1415 expectedSettings := charm.Settings{"blog-title": "yaml-title"} 1416 obtainedSettings, err := application.ConfigSettings() 1417 c.Assert(err, jc.ErrorIsNil) 1418 c.Assert(obtainedSettings, gc.DeepEquals, expectedSettings) 1419 1420 // Check the constraints. 1421 obtainedConstraints, err := application.Constraints() 1422 c.Assert(err, jc.ErrorIsNil) 1423 c.Assert(obtainedConstraints, gc.DeepEquals, cons) 1424 } 1425 1426 func (s *serviceSuite) TestServiceUpdateNoParams(c *gc.C) { 1427 s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 1428 1429 // Calling Update with no parameters set is a no-op. 1430 args := params.ApplicationUpdate{ApplicationName: "wordpress"} 1431 err := s.applicationAPI.Update(args) 1432 c.Assert(err, jc.ErrorIsNil) 1433 } 1434 1435 func (s *serviceSuite) TestServiceUpdateNoService(c *gc.C) { 1436 err := s.applicationAPI.Update(params.ApplicationUpdate{}) 1437 c.Assert(err, gc.ErrorMatches, `"" is not a valid application name`) 1438 } 1439 1440 func (s *serviceSuite) TestServiceUpdateInvalidService(c *gc.C) { 1441 args := params.ApplicationUpdate{ApplicationName: "no-such-service"} 1442 err := s.applicationAPI.Update(args) 1443 c.Assert(err, gc.ErrorMatches, `application "no-such-service" not found`) 1444 } 1445 1446 var ( 1447 validSetTestValue = "a value with spaces\nand newline\nand UTF-8 characters: \U0001F604 / \U0001F44D" 1448 ) 1449 1450 func (s *serviceSuite) TestServiceSet(c *gc.C) { 1451 dummy := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 1452 1453 err := s.applicationAPI.Set(params.ApplicationSet{ApplicationName: "dummy", Options: map[string]string{ 1454 "title": "foobar", 1455 "username": validSetTestValue, 1456 }}) 1457 c.Assert(err, jc.ErrorIsNil) 1458 settings, err := dummy.ConfigSettings() 1459 c.Assert(err, jc.ErrorIsNil) 1460 c.Assert(settings, gc.DeepEquals, charm.Settings{ 1461 "title": "foobar", 1462 "username": validSetTestValue, 1463 }) 1464 1465 err = s.applicationAPI.Set(params.ApplicationSet{ApplicationName: "dummy", Options: map[string]string{ 1466 "title": "barfoo", 1467 "username": "", 1468 }}) 1469 c.Assert(err, jc.ErrorIsNil) 1470 settings, err = dummy.ConfigSettings() 1471 c.Assert(err, jc.ErrorIsNil) 1472 c.Assert(settings, gc.DeepEquals, charm.Settings{ 1473 "title": "barfoo", 1474 "username": "", 1475 }) 1476 } 1477 1478 func (s *serviceSuite) assertServiceSetBlocked(c *gc.C, dummy *state.Application, msg string) { 1479 err := s.applicationAPI.Set(params.ApplicationSet{ 1480 ApplicationName: "dummy", 1481 Options: map[string]string{ 1482 "title": "foobar", 1483 "username": validSetTestValue}}) 1484 s.AssertBlocked(c, err, msg) 1485 } 1486 1487 func (s *serviceSuite) assertServiceSet(c *gc.C, dummy *state.Application) { 1488 err := s.applicationAPI.Set(params.ApplicationSet{ 1489 ApplicationName: "dummy", 1490 Options: map[string]string{ 1491 "title": "foobar", 1492 "username": validSetTestValue}}) 1493 c.Assert(err, jc.ErrorIsNil) 1494 settings, err := dummy.ConfigSettings() 1495 c.Assert(err, jc.ErrorIsNil) 1496 c.Assert(settings, gc.DeepEquals, charm.Settings{ 1497 "title": "foobar", 1498 "username": validSetTestValue, 1499 }) 1500 } 1501 1502 func (s *serviceSuite) TestBlockDestroyServiceSet(c *gc.C) { 1503 dummy := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 1504 s.BlockDestroyModel(c, "TestBlockDestroyServiceSet") 1505 s.assertServiceSet(c, dummy) 1506 } 1507 1508 func (s *serviceSuite) TestBlockRemoveServiceSet(c *gc.C) { 1509 dummy := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 1510 s.BlockRemoveObject(c, "TestBlockRemoveServiceSet") 1511 s.assertServiceSet(c, dummy) 1512 } 1513 1514 func (s *serviceSuite) TestBlockChangesServiceSet(c *gc.C) { 1515 dummy := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 1516 s.BlockAllChanges(c, "TestBlockChangesServiceSet") 1517 s.assertServiceSetBlocked(c, dummy, "TestBlockChangesServiceSet") 1518 } 1519 1520 func (s *serviceSuite) TestServerUnset(c *gc.C) { 1521 dummy := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 1522 1523 err := s.applicationAPI.Set(params.ApplicationSet{ApplicationName: "dummy", Options: map[string]string{ 1524 "title": "foobar", 1525 "username": "user name", 1526 }}) 1527 c.Assert(err, jc.ErrorIsNil) 1528 settings, err := dummy.ConfigSettings() 1529 c.Assert(err, jc.ErrorIsNil) 1530 c.Assert(settings, gc.DeepEquals, charm.Settings{ 1531 "title": "foobar", 1532 "username": "user name", 1533 }) 1534 1535 err = s.applicationAPI.Unset(params.ApplicationUnset{ApplicationName: "dummy", Options: []string{"username"}}) 1536 c.Assert(err, jc.ErrorIsNil) 1537 settings, err = dummy.ConfigSettings() 1538 c.Assert(err, jc.ErrorIsNil) 1539 c.Assert(settings, gc.DeepEquals, charm.Settings{ 1540 "title": "foobar", 1541 }) 1542 } 1543 1544 func (s *serviceSuite) setupServerUnsetBlocked(c *gc.C) *state.Application { 1545 dummy := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 1546 1547 err := s.applicationAPI.Set(params.ApplicationSet{ 1548 ApplicationName: "dummy", 1549 Options: map[string]string{ 1550 "title": "foobar", 1551 "username": "user name", 1552 }}) 1553 c.Assert(err, jc.ErrorIsNil) 1554 settings, err := dummy.ConfigSettings() 1555 c.Assert(err, jc.ErrorIsNil) 1556 c.Assert(settings, gc.DeepEquals, charm.Settings{ 1557 "title": "foobar", 1558 "username": "user name", 1559 }) 1560 return dummy 1561 } 1562 1563 func (s *serviceSuite) assertServerUnset(c *gc.C, dummy *state.Application) { 1564 err := s.applicationAPI.Unset(params.ApplicationUnset{ 1565 ApplicationName: "dummy", 1566 Options: []string{"username"}, 1567 }) 1568 c.Assert(err, jc.ErrorIsNil) 1569 settings, err := dummy.ConfigSettings() 1570 c.Assert(err, jc.ErrorIsNil) 1571 c.Assert(settings, gc.DeepEquals, charm.Settings{ 1572 "title": "foobar", 1573 }) 1574 } 1575 1576 func (s *serviceSuite) assertServerUnsetBlocked(c *gc.C, dummy *state.Application, msg string) { 1577 err := s.applicationAPI.Unset(params.ApplicationUnset{ 1578 ApplicationName: "dummy", 1579 Options: []string{"username"}, 1580 }) 1581 s.AssertBlocked(c, err, msg) 1582 } 1583 1584 func (s *serviceSuite) TestBlockDestroyServerUnset(c *gc.C) { 1585 dummy := s.setupServerUnsetBlocked(c) 1586 s.BlockDestroyModel(c, "TestBlockDestroyServerUnset") 1587 s.assertServerUnset(c, dummy) 1588 } 1589 1590 func (s *serviceSuite) TestBlockRemoveServerUnset(c *gc.C) { 1591 dummy := s.setupServerUnsetBlocked(c) 1592 s.BlockRemoveObject(c, "TestBlockRemoveServerUnset") 1593 s.assertServerUnset(c, dummy) 1594 } 1595 1596 func (s *serviceSuite) TestBlockChangesServerUnset(c *gc.C) { 1597 dummy := s.setupServerUnsetBlocked(c) 1598 s.BlockAllChanges(c, "TestBlockChangesServerUnset") 1599 s.assertServerUnsetBlocked(c, dummy, "TestBlockChangesServerUnset") 1600 } 1601 1602 var clientAddServiceUnitsTests = []struct { 1603 about string 1604 service string // if not set, defaults to 'dummy' 1605 expected []string 1606 to string 1607 err string 1608 }{ 1609 { 1610 about: "returns unit names", 1611 expected: []string{"dummy/0", "dummy/1", "dummy/2"}, 1612 }, 1613 { 1614 about: "fails trying to add zero units", 1615 err: "must add at least one unit", 1616 }, 1617 { 1618 // Note: chained-state, we add 1 unit here, but the 3 units 1619 // from the first condition still exist 1620 about: "force the unit onto bootstrap machine", 1621 expected: []string{"dummy/3"}, 1622 to: "0", 1623 }, 1624 { 1625 about: "unknown service name", 1626 service: "unknown-service", 1627 err: `application "unknown-service" not found`, 1628 }, 1629 } 1630 1631 func (s *serviceSuite) TestClientAddServiceUnits(c *gc.C) { 1632 s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 1633 for i, t := range clientAddServiceUnitsTests { 1634 c.Logf("test %d. %s", i, t.about) 1635 serviceName := t.service 1636 if serviceName == "" { 1637 serviceName = "dummy" 1638 } 1639 args := params.AddApplicationUnits{ 1640 ApplicationName: serviceName, 1641 NumUnits: len(t.expected), 1642 } 1643 if t.to != "" { 1644 args.Placement = []*instance.Placement{instance.MustParsePlacement(t.to)} 1645 } 1646 result, err := s.applicationAPI.AddUnits(args) 1647 if t.err != "" { 1648 c.Assert(err, gc.ErrorMatches, t.err) 1649 continue 1650 } 1651 c.Assert(err, jc.ErrorIsNil) 1652 c.Assert(result.Units, gc.DeepEquals, t.expected) 1653 } 1654 // Test that we actually assigned the unit to machine 0 1655 forcedUnit, err := s.BackingState.Unit("dummy/3") 1656 c.Assert(err, jc.ErrorIsNil) 1657 assignedMachine, err := forcedUnit.AssignedMachineId() 1658 c.Assert(err, jc.ErrorIsNil) 1659 c.Assert(assignedMachine, gc.Equals, "0") 1660 } 1661 1662 func (s *serviceSuite) TestAddServiceUnitsToNewContainer(c *gc.C) { 1663 svc := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 1664 machine, err := s.State.AddMachine("quantal", state.JobHostUnits) 1665 c.Assert(err, jc.ErrorIsNil) 1666 1667 _, err = s.applicationAPI.AddUnits(params.AddApplicationUnits{ 1668 ApplicationName: "dummy", 1669 NumUnits: 1, 1670 Placement: []*instance.Placement{instance.MustParsePlacement("lxd:" + machine.Id())}, 1671 }) 1672 c.Assert(err, jc.ErrorIsNil) 1673 1674 units, err := svc.AllUnits() 1675 c.Assert(err, jc.ErrorIsNil) 1676 mid, err := units[0].AssignedMachineId() 1677 c.Assert(err, jc.ErrorIsNil) 1678 c.Assert(mid, gc.Equals, machine.Id()+"/lxd/0") 1679 } 1680 1681 var addServiceUnitTests = []struct { 1682 about string 1683 service string // if not set, defaults to 'dummy' 1684 expected []string 1685 machineIds []string 1686 placement []*instance.Placement 1687 err string 1688 }{ 1689 { 1690 about: "valid placement directives", 1691 expected: []string{"dummy/0"}, 1692 placement: []*instance.Placement{{"deadbeef-0bad-400d-8000-4b1d0d06f00d", "valid"}}, 1693 machineIds: []string{"1"}, 1694 }, { 1695 about: "direct machine assignment placement directive", 1696 expected: []string{"dummy/1", "dummy/2"}, 1697 placement: []*instance.Placement{{"#", "1"}, {"lxd", "1"}}, 1698 machineIds: []string{"1", "1/lxd/0"}, 1699 }, { 1700 about: "invalid placement directive", 1701 err: ".* invalid placement is invalid", 1702 expected: []string{"dummy/3"}, 1703 placement: []*instance.Placement{{"deadbeef-0bad-400d-8000-4b1d0d06f00d", "invalid"}}, 1704 }, 1705 } 1706 1707 func (s *serviceSuite) TestAddServiceUnits(c *gc.C) { 1708 s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 1709 // Add a machine for the units to be placed on. 1710 _, err := s.State.AddMachine("quantal", state.JobHostUnits) 1711 c.Assert(err, jc.ErrorIsNil) 1712 for i, t := range addServiceUnitTests { 1713 c.Logf("test %d. %s", i, t.about) 1714 serviceName := t.service 1715 if serviceName == "" { 1716 serviceName = "dummy" 1717 } 1718 result, err := s.applicationAPI.AddUnits(params.AddApplicationUnits{ 1719 ApplicationName: serviceName, 1720 NumUnits: len(t.expected), 1721 Placement: t.placement, 1722 }) 1723 if t.err != "" { 1724 c.Assert(err, gc.ErrorMatches, t.err) 1725 continue 1726 } 1727 c.Assert(err, jc.ErrorIsNil) 1728 c.Assert(result.Units, gc.DeepEquals, t.expected) 1729 for i, unitName := range result.Units { 1730 u, err := s.BackingState.Unit(unitName) 1731 c.Assert(err, jc.ErrorIsNil) 1732 assignedMachine, err := u.AssignedMachineId() 1733 c.Assert(err, jc.ErrorIsNil) 1734 c.Assert(assignedMachine, gc.Equals, t.machineIds[i]) 1735 } 1736 } 1737 } 1738 1739 func (s *serviceSuite) assertAddServiceUnits(c *gc.C) { 1740 result, err := s.applicationAPI.AddUnits(params.AddApplicationUnits{ 1741 ApplicationName: "dummy", 1742 NumUnits: 3, 1743 }) 1744 c.Assert(err, jc.ErrorIsNil) 1745 c.Assert(result.Units, gc.DeepEquals, []string{"dummy/0", "dummy/1", "dummy/2"}) 1746 1747 // Test that we actually assigned the unit to machine 0 1748 forcedUnit, err := s.BackingState.Unit("dummy/0") 1749 c.Assert(err, jc.ErrorIsNil) 1750 assignedMachine, err := forcedUnit.AssignedMachineId() 1751 c.Assert(err, jc.ErrorIsNil) 1752 c.Assert(assignedMachine, gc.Equals, "0") 1753 } 1754 1755 func (s *serviceSuite) TestServiceCharmRelations(c *gc.C) { 1756 s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 1757 s.AddTestingService(c, "logging", s.AddTestingCharm(c, "logging")) 1758 eps, err := s.State.InferEndpoints("logging", "wordpress") 1759 c.Assert(err, jc.ErrorIsNil) 1760 _, err = s.State.AddRelation(eps...) 1761 c.Assert(err, jc.ErrorIsNil) 1762 1763 _, err = s.applicationAPI.CharmRelations(params.ApplicationCharmRelations{"blah"}) 1764 c.Assert(err, gc.ErrorMatches, `application "blah" not found`) 1765 1766 result, err := s.applicationAPI.CharmRelations(params.ApplicationCharmRelations{"wordpress"}) 1767 c.Assert(err, jc.ErrorIsNil) 1768 c.Assert(result.CharmRelations, gc.DeepEquals, []string{ 1769 "cache", "db", "juju-info", "logging-dir", "monitoring-port", "url", 1770 }) 1771 } 1772 1773 func (s *serviceSuite) assertAddServiceUnitsBlocked(c *gc.C, msg string) { 1774 _, err := s.applicationAPI.AddUnits(params.AddApplicationUnits{ 1775 ApplicationName: "dummy", 1776 NumUnits: 3, 1777 }) 1778 s.AssertBlocked(c, err, msg) 1779 } 1780 1781 func (s *serviceSuite) TestBlockDestroyAddServiceUnits(c *gc.C) { 1782 s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 1783 s.BlockDestroyModel(c, "TestBlockDestroyAddServiceUnits") 1784 s.assertAddServiceUnits(c) 1785 } 1786 1787 func (s *serviceSuite) TestBlockRemoveAddServiceUnits(c *gc.C) { 1788 s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 1789 s.BlockRemoveObject(c, "TestBlockRemoveAddServiceUnits") 1790 s.assertAddServiceUnits(c) 1791 } 1792 1793 func (s *serviceSuite) TestBlockChangeAddServiceUnits(c *gc.C) { 1794 s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 1795 s.BlockAllChanges(c, "TestBlockChangeAddServiceUnits") 1796 s.assertAddServiceUnitsBlocked(c, "TestBlockChangeAddServiceUnits") 1797 } 1798 1799 func (s *serviceSuite) TestAddUnitToMachineNotFound(c *gc.C) { 1800 s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 1801 _, err := s.applicationAPI.AddUnits(params.AddApplicationUnits{ 1802 ApplicationName: "dummy", 1803 NumUnits: 3, 1804 Placement: []*instance.Placement{instance.MustParsePlacement("42")}, 1805 }) 1806 c.Assert(err, gc.ErrorMatches, `adding new machine to host unit "dummy/0": machine 42 not found`) 1807 } 1808 1809 func (s *serviceSuite) TestServiceExpose(c *gc.C) { 1810 charm := s.AddTestingCharm(c, "dummy") 1811 serviceNames := []string{"dummy-service", "exposed-service"} 1812 svcs := make([]*state.Application, len(serviceNames)) 1813 var err error 1814 for i, name := range serviceNames { 1815 svcs[i] = s.AddTestingService(c, name, charm) 1816 c.Assert(svcs[i].IsExposed(), jc.IsFalse) 1817 } 1818 err = svcs[1].SetExposed() 1819 c.Assert(err, jc.ErrorIsNil) 1820 c.Assert(svcs[1].IsExposed(), jc.IsTrue) 1821 for i, t := range serviceExposeTests { 1822 c.Logf("test %d. %s", i, t.about) 1823 err = s.applicationAPI.Expose(params.ApplicationExpose{t.service}) 1824 if t.err != "" { 1825 c.Assert(err, gc.ErrorMatches, t.err) 1826 } else { 1827 c.Assert(err, jc.ErrorIsNil) 1828 application, err := s.State.Application(t.service) 1829 c.Assert(err, jc.ErrorIsNil) 1830 c.Assert(application.IsExposed(), gc.Equals, t.exposed) 1831 } 1832 } 1833 } 1834 1835 func (s *serviceSuite) setupServiceExpose(c *gc.C) { 1836 charm := s.AddTestingCharm(c, "dummy") 1837 serviceNames := []string{"dummy-service", "exposed-service"} 1838 svcs := make([]*state.Application, len(serviceNames)) 1839 var err error 1840 for i, name := range serviceNames { 1841 svcs[i] = s.AddTestingService(c, name, charm) 1842 c.Assert(svcs[i].IsExposed(), jc.IsFalse) 1843 } 1844 err = svcs[1].SetExposed() 1845 c.Assert(err, jc.ErrorIsNil) 1846 c.Assert(svcs[1].IsExposed(), jc.IsTrue) 1847 } 1848 1849 var serviceExposeTests = []struct { 1850 about string 1851 service string 1852 err string 1853 exposed bool 1854 }{ 1855 { 1856 about: "unknown service name", 1857 service: "unknown-service", 1858 err: `application "unknown-service" not found`, 1859 }, 1860 { 1861 about: "expose a service", 1862 service: "dummy-service", 1863 exposed: true, 1864 }, 1865 { 1866 about: "expose an already exposed service", 1867 service: "exposed-service", 1868 exposed: true, 1869 }, 1870 } 1871 1872 func (s *serviceSuite) assertServiceExpose(c *gc.C) { 1873 for i, t := range serviceExposeTests { 1874 c.Logf("test %d. %s", i, t.about) 1875 err := s.applicationAPI.Expose(params.ApplicationExpose{t.service}) 1876 if t.err != "" { 1877 c.Assert(err, gc.ErrorMatches, t.err) 1878 } else { 1879 c.Assert(err, jc.ErrorIsNil) 1880 application, err := s.State.Application(t.service) 1881 c.Assert(err, jc.ErrorIsNil) 1882 c.Assert(application.IsExposed(), gc.Equals, t.exposed) 1883 } 1884 } 1885 } 1886 1887 func (s *serviceSuite) assertServiceExposeBlocked(c *gc.C, msg string) { 1888 for i, t := range serviceExposeTests { 1889 c.Logf("test %d. %s", i, t.about) 1890 err := s.applicationAPI.Expose(params.ApplicationExpose{t.service}) 1891 s.AssertBlocked(c, err, msg) 1892 } 1893 } 1894 1895 func (s *serviceSuite) TestBlockDestroyServiceExpose(c *gc.C) { 1896 s.setupServiceExpose(c) 1897 s.BlockDestroyModel(c, "TestBlockDestroyServiceExpose") 1898 s.assertServiceExpose(c) 1899 } 1900 1901 func (s *serviceSuite) TestBlockRemoveServiceExpose(c *gc.C) { 1902 s.setupServiceExpose(c) 1903 s.BlockRemoveObject(c, "TestBlockRemoveServiceExpose") 1904 s.assertServiceExpose(c) 1905 } 1906 1907 func (s *serviceSuite) TestBlockChangesServiceExpose(c *gc.C) { 1908 s.setupServiceExpose(c) 1909 s.BlockAllChanges(c, "TestBlockChangesServiceExpose") 1910 s.assertServiceExposeBlocked(c, "TestBlockChangesServiceExpose") 1911 } 1912 1913 var serviceUnexposeTests = []struct { 1914 about string 1915 service string 1916 err string 1917 initial bool 1918 expected bool 1919 }{ 1920 { 1921 about: "unknown service name", 1922 service: "unknown-service", 1923 err: `application "unknown-service" not found`, 1924 }, 1925 { 1926 about: "unexpose a service", 1927 service: "dummy-service", 1928 initial: true, 1929 expected: false, 1930 }, 1931 { 1932 about: "unexpose an already unexposed service", 1933 service: "dummy-service", 1934 initial: false, 1935 expected: false, 1936 }, 1937 } 1938 1939 func (s *serviceSuite) TestServiceUnexpose(c *gc.C) { 1940 charm := s.AddTestingCharm(c, "dummy") 1941 for i, t := range serviceUnexposeTests { 1942 c.Logf("test %d. %s", i, t.about) 1943 svc := s.AddTestingService(c, "dummy-service", charm) 1944 if t.initial { 1945 svc.SetExposed() 1946 } 1947 c.Assert(svc.IsExposed(), gc.Equals, t.initial) 1948 err := s.applicationAPI.Unexpose(params.ApplicationUnexpose{t.service}) 1949 if t.err == "" { 1950 c.Assert(err, jc.ErrorIsNil) 1951 svc.Refresh() 1952 c.Assert(svc.IsExposed(), gc.Equals, t.expected) 1953 } else { 1954 c.Assert(err, gc.ErrorMatches, t.err) 1955 } 1956 err = svc.Destroy() 1957 c.Assert(err, jc.ErrorIsNil) 1958 } 1959 } 1960 1961 func (s *serviceSuite) setupServiceUnexpose(c *gc.C) *state.Application { 1962 charm := s.AddTestingCharm(c, "dummy") 1963 svc := s.AddTestingService(c, "dummy-service", charm) 1964 svc.SetExposed() 1965 c.Assert(svc.IsExposed(), gc.Equals, true) 1966 return svc 1967 } 1968 1969 func (s *serviceSuite) assertServiceUnexpose(c *gc.C, svc *state.Application) { 1970 err := s.applicationAPI.Unexpose(params.ApplicationUnexpose{"dummy-service"}) 1971 c.Assert(err, jc.ErrorIsNil) 1972 svc.Refresh() 1973 c.Assert(svc.IsExposed(), gc.Equals, false) 1974 err = svc.Destroy() 1975 c.Assert(err, jc.ErrorIsNil) 1976 } 1977 1978 func (s *serviceSuite) assertServiceUnexposeBlocked(c *gc.C, svc *state.Application, msg string) { 1979 err := s.applicationAPI.Unexpose(params.ApplicationUnexpose{"dummy-service"}) 1980 s.AssertBlocked(c, err, msg) 1981 err = svc.Destroy() 1982 c.Assert(err, jc.ErrorIsNil) 1983 } 1984 1985 func (s *serviceSuite) TestBlockDestroyServiceUnexpose(c *gc.C) { 1986 svc := s.setupServiceUnexpose(c) 1987 s.BlockDestroyModel(c, "TestBlockDestroyServiceUnexpose") 1988 s.assertServiceUnexpose(c, svc) 1989 } 1990 1991 func (s *serviceSuite) TestBlockRemoveServiceUnexpose(c *gc.C) { 1992 svc := s.setupServiceUnexpose(c) 1993 s.BlockRemoveObject(c, "TestBlockRemoveServiceUnexpose") 1994 s.assertServiceUnexpose(c, svc) 1995 } 1996 1997 func (s *serviceSuite) TestBlockChangesServiceUnexpose(c *gc.C) { 1998 svc := s.setupServiceUnexpose(c) 1999 s.BlockAllChanges(c, "TestBlockChangesServiceUnexpose") 2000 s.assertServiceUnexposeBlocked(c, svc, "TestBlockChangesServiceUnexpose") 2001 } 2002 2003 var serviceDestroyTests = []struct { 2004 about string 2005 service string 2006 err string 2007 }{ 2008 { 2009 about: "unknown service name", 2010 service: "unknown-service", 2011 err: `application "unknown-service" not found`, 2012 }, 2013 { 2014 about: "destroy a service", 2015 service: "dummy-service", 2016 }, 2017 { 2018 about: "destroy an already destroyed service", 2019 service: "dummy-service", 2020 err: `application "dummy-service" not found`, 2021 }, 2022 } 2023 2024 func (s *serviceSuite) TestServiceDestroy(c *gc.C) { 2025 s.AddTestingService(c, "dummy-service", s.AddTestingCharm(c, "dummy")) 2026 for i, t := range serviceDestroyTests { 2027 c.Logf("test %d. %s", i, t.about) 2028 err := s.applicationAPI.Destroy(params.ApplicationDestroy{t.service}) 2029 if t.err != "" { 2030 c.Assert(err, gc.ErrorMatches, t.err) 2031 } else { 2032 c.Assert(err, jc.ErrorIsNil) 2033 } 2034 } 2035 2036 // Now do Destroy on a service with units. Destroy will 2037 // cause the service to be not-Alive, but will not remove its 2038 // document. 2039 s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 2040 serviceName := "wordpress" 2041 application, err := s.State.Application(serviceName) 2042 c.Assert(err, jc.ErrorIsNil) 2043 err = s.applicationAPI.Destroy(params.ApplicationDestroy{serviceName}) 2044 c.Assert(err, jc.ErrorIsNil) 2045 err = application.Refresh() 2046 c.Assert(err, jc.Satisfies, errors.IsNotFound) 2047 } 2048 2049 func assertLife(c *gc.C, entity state.Living, life state.Life) { 2050 err := entity.Refresh() 2051 c.Assert(err, jc.ErrorIsNil) 2052 c.Assert(entity.Life(), gc.Equals, life) 2053 } 2054 2055 func (s *serviceSuite) TestBlockServiceDestroy(c *gc.C) { 2056 s.AddTestingService(c, "dummy-service", s.AddTestingCharm(c, "dummy")) 2057 2058 // block remove-objects 2059 s.BlockRemoveObject(c, "TestBlockServiceDestroy") 2060 err := s.applicationAPI.Destroy(params.ApplicationDestroy{"dummy-service"}) 2061 s.AssertBlocked(c, err, "TestBlockServiceDestroy") 2062 // Tests may have invalid service names. 2063 application, err := s.State.Application("dummy-service") 2064 if err == nil { 2065 // For valid service names, check that service is alive :-) 2066 assertLife(c, application, state.Alive) 2067 } 2068 } 2069 2070 func (s *serviceSuite) TestDestroyPrincipalUnits(c *gc.C) { 2071 wordpress := s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 2072 units := make([]*state.Unit, 5) 2073 for i := range units { 2074 unit, err := wordpress.AddUnit() 2075 c.Assert(err, jc.ErrorIsNil) 2076 now := time.Now() 2077 sInfo := status.StatusInfo{ 2078 Status: status.Idle, 2079 Message: "", 2080 Since: &now, 2081 } 2082 err = unit.SetAgentStatus(sInfo) 2083 c.Assert(err, jc.ErrorIsNil) 2084 units[i] = unit 2085 } 2086 s.assertDestroyPrincipalUnits(c, units) 2087 } 2088 2089 func (s *serviceSuite) TestDestroySubordinateUnits(c *gc.C) { 2090 wordpress := s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 2091 wordpress0, err := wordpress.AddUnit() 2092 c.Assert(err, jc.ErrorIsNil) 2093 s.AddTestingService(c, "logging", s.AddTestingCharm(c, "logging")) 2094 eps, err := s.State.InferEndpoints("logging", "wordpress") 2095 c.Assert(err, jc.ErrorIsNil) 2096 rel, err := s.State.AddRelation(eps...) 2097 c.Assert(err, jc.ErrorIsNil) 2098 ru, err := rel.Unit(wordpress0) 2099 c.Assert(err, jc.ErrorIsNil) 2100 err = ru.EnterScope(nil) 2101 c.Assert(err, jc.ErrorIsNil) 2102 logging0, err := s.State.Unit("logging/0") 2103 c.Assert(err, jc.ErrorIsNil) 2104 2105 // Try to destroy the subordinate alone; check it fails. 2106 err = s.applicationAPI.DestroyUnits(params.DestroyApplicationUnits{ 2107 UnitNames: []string{"logging/0"}, 2108 }) 2109 c.Assert(err, gc.ErrorMatches, `no units were destroyed: unit "logging/0" is a subordinate`) 2110 assertLife(c, logging0, state.Alive) 2111 2112 s.assertDestroySubordinateUnits(c, wordpress0, logging0) 2113 } 2114 2115 func (s *serviceSuite) assertDestroyPrincipalUnits(c *gc.C, units []*state.Unit) { 2116 // Destroy 2 of them; check they become Dying. 2117 err := s.applicationAPI.DestroyUnits(params.DestroyApplicationUnits{ 2118 UnitNames: []string{"wordpress/0", "wordpress/1"}, 2119 }) 2120 c.Assert(err, jc.ErrorIsNil) 2121 assertLife(c, units[0], state.Dying) 2122 assertLife(c, units[1], state.Dying) 2123 2124 // Try to destroy an Alive one and a Dying one; check 2125 // it destroys the Alive one and ignores the Dying one. 2126 err = s.applicationAPI.DestroyUnits(params.DestroyApplicationUnits{ 2127 UnitNames: []string{"wordpress/2", "wordpress/0"}, 2128 }) 2129 c.Assert(err, jc.ErrorIsNil) 2130 assertLife(c, units[2], state.Dying) 2131 2132 // Try to destroy an Alive one along with a nonexistent one; check that 2133 // the valid instruction is followed but the invalid one is warned about. 2134 err = s.applicationAPI.DestroyUnits(params.DestroyApplicationUnits{ 2135 UnitNames: []string{"boojum/123", "wordpress/3"}, 2136 }) 2137 c.Assert(err, gc.ErrorMatches, `some units were not destroyed: unit "boojum/123" does not exist`) 2138 assertLife(c, units[3], state.Dying) 2139 2140 // Make one Dead, and destroy an Alive one alongside it; check no errors. 2141 wp0, err := s.State.Unit("wordpress/0") 2142 c.Assert(err, jc.ErrorIsNil) 2143 err = wp0.EnsureDead() 2144 c.Assert(err, jc.ErrorIsNil) 2145 err = s.applicationAPI.DestroyUnits(params.DestroyApplicationUnits{ 2146 UnitNames: []string{"wordpress/0", "wordpress/4"}, 2147 }) 2148 c.Assert(err, jc.ErrorIsNil) 2149 assertLife(c, units[0], state.Dead) 2150 assertLife(c, units[4], state.Dying) 2151 } 2152 2153 func (s *serviceSuite) setupDestroyPrincipalUnits(c *gc.C) []*state.Unit { 2154 units := make([]*state.Unit, 5) 2155 wordpress := s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 2156 for i := range units { 2157 unit, err := wordpress.AddUnit() 2158 c.Assert(err, jc.ErrorIsNil) 2159 now := time.Now() 2160 sInfo := status.StatusInfo{ 2161 Status: status.Idle, 2162 Message: "", 2163 Since: &now, 2164 } 2165 err = unit.SetAgentStatus(sInfo) 2166 c.Assert(err, jc.ErrorIsNil) 2167 units[i] = unit 2168 } 2169 return units 2170 } 2171 2172 func (s *serviceSuite) assertBlockedErrorAndLiveliness( 2173 c *gc.C, 2174 err error, 2175 msg string, 2176 living1 state.Living, 2177 living2 state.Living, 2178 living3 state.Living, 2179 living4 state.Living, 2180 ) { 2181 s.AssertBlocked(c, err, msg) 2182 assertLife(c, living1, state.Alive) 2183 assertLife(c, living2, state.Alive) 2184 assertLife(c, living3, state.Alive) 2185 assertLife(c, living4, state.Alive) 2186 } 2187 2188 func (s *serviceSuite) TestBlockChangesDestroyPrincipalUnits(c *gc.C) { 2189 units := s.setupDestroyPrincipalUnits(c) 2190 s.BlockAllChanges(c, "TestBlockChangesDestroyPrincipalUnits") 2191 err := s.applicationAPI.DestroyUnits(params.DestroyApplicationUnits{ 2192 UnitNames: []string{"wordpress/0", "wordpress/1"}, 2193 }) 2194 s.assertBlockedErrorAndLiveliness(c, err, "TestBlockChangesDestroyPrincipalUnits", units[0], units[1], units[2], units[3]) 2195 } 2196 2197 func (s *serviceSuite) TestBlockRemoveDestroyPrincipalUnits(c *gc.C) { 2198 units := s.setupDestroyPrincipalUnits(c) 2199 s.BlockRemoveObject(c, "TestBlockRemoveDestroyPrincipalUnits") 2200 err := s.applicationAPI.DestroyUnits(params.DestroyApplicationUnits{ 2201 UnitNames: []string{"wordpress/0", "wordpress/1"}, 2202 }) 2203 s.assertBlockedErrorAndLiveliness(c, err, "TestBlockRemoveDestroyPrincipalUnits", units[0], units[1], units[2], units[3]) 2204 } 2205 2206 func (s *serviceSuite) TestBlockDestroyDestroyPrincipalUnits(c *gc.C) { 2207 units := s.setupDestroyPrincipalUnits(c) 2208 s.BlockDestroyModel(c, "TestBlockDestroyDestroyPrincipalUnits") 2209 err := s.applicationAPI.DestroyUnits(params.DestroyApplicationUnits{ 2210 UnitNames: []string{"wordpress/0", "wordpress/1"}, 2211 }) 2212 c.Assert(err, jc.ErrorIsNil) 2213 assertLife(c, units[0], state.Dying) 2214 assertLife(c, units[1], state.Dying) 2215 } 2216 2217 func (s *serviceSuite) assertDestroySubordinateUnits(c *gc.C, wordpress0, logging0 *state.Unit) { 2218 // Try to destroy the principal and the subordinate together; check it warns 2219 // about the subordinate, but destroys the one it can. (The principal unit 2220 // agent will be responsible for destroying the subordinate.) 2221 err := s.applicationAPI.DestroyUnits(params.DestroyApplicationUnits{ 2222 UnitNames: []string{"wordpress/0", "logging/0"}, 2223 }) 2224 c.Assert(err, gc.ErrorMatches, `some units were not destroyed: unit "logging/0" is a subordinate`) 2225 assertLife(c, wordpress0, state.Dying) 2226 assertLife(c, logging0, state.Alive) 2227 } 2228 2229 func (s *serviceSuite) TestBlockRemoveDestroySubordinateUnits(c *gc.C) { 2230 wordpress := s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 2231 wordpress0, err := wordpress.AddUnit() 2232 c.Assert(err, jc.ErrorIsNil) 2233 s.AddTestingService(c, "logging", s.AddTestingCharm(c, "logging")) 2234 eps, err := s.State.InferEndpoints("logging", "wordpress") 2235 c.Assert(err, jc.ErrorIsNil) 2236 rel, err := s.State.AddRelation(eps...) 2237 c.Assert(err, jc.ErrorIsNil) 2238 ru, err := rel.Unit(wordpress0) 2239 c.Assert(err, jc.ErrorIsNil) 2240 err = ru.EnterScope(nil) 2241 c.Assert(err, jc.ErrorIsNil) 2242 logging0, err := s.State.Unit("logging/0") 2243 c.Assert(err, jc.ErrorIsNil) 2244 2245 s.BlockRemoveObject(c, "TestBlockRemoveDestroySubordinateUnits") 2246 // Try to destroy the subordinate alone; check it fails. 2247 err = s.applicationAPI.DestroyUnits(params.DestroyApplicationUnits{ 2248 UnitNames: []string{"logging/0"}, 2249 }) 2250 s.AssertBlocked(c, err, "TestBlockRemoveDestroySubordinateUnits") 2251 assertLife(c, rel, state.Alive) 2252 assertLife(c, wordpress0, state.Alive) 2253 assertLife(c, logging0, state.Alive) 2254 2255 err = s.applicationAPI.DestroyUnits(params.DestroyApplicationUnits{ 2256 UnitNames: []string{"wordpress/0", "logging/0"}, 2257 }) 2258 s.AssertBlocked(c, err, "TestBlockRemoveDestroySubordinateUnits") 2259 assertLife(c, wordpress0, state.Alive) 2260 assertLife(c, logging0, state.Alive) 2261 assertLife(c, rel, state.Alive) 2262 } 2263 2264 func (s *serviceSuite) TestBlockChangesDestroySubordinateUnits(c *gc.C) { 2265 wordpress := s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 2266 wordpress0, err := wordpress.AddUnit() 2267 c.Assert(err, jc.ErrorIsNil) 2268 s.AddTestingService(c, "logging", s.AddTestingCharm(c, "logging")) 2269 eps, err := s.State.InferEndpoints("logging", "wordpress") 2270 c.Assert(err, jc.ErrorIsNil) 2271 rel, err := s.State.AddRelation(eps...) 2272 c.Assert(err, jc.ErrorIsNil) 2273 ru, err := rel.Unit(wordpress0) 2274 c.Assert(err, jc.ErrorIsNil) 2275 err = ru.EnterScope(nil) 2276 c.Assert(err, jc.ErrorIsNil) 2277 logging0, err := s.State.Unit("logging/0") 2278 c.Assert(err, jc.ErrorIsNil) 2279 2280 s.BlockAllChanges(c, "TestBlockChangesDestroySubordinateUnits") 2281 // Try to destroy the subordinate alone; check it fails. 2282 err = s.applicationAPI.DestroyUnits(params.DestroyApplicationUnits{ 2283 UnitNames: []string{"logging/0"}, 2284 }) 2285 s.AssertBlocked(c, err, "TestBlockChangesDestroySubordinateUnits") 2286 assertLife(c, rel, state.Alive) 2287 assertLife(c, wordpress0, state.Alive) 2288 assertLife(c, logging0, state.Alive) 2289 2290 err = s.applicationAPI.DestroyUnits(params.DestroyApplicationUnits{ 2291 UnitNames: []string{"wordpress/0", "logging/0"}, 2292 }) 2293 s.AssertBlocked(c, err, "TestBlockChangesDestroySubordinateUnits") 2294 assertLife(c, wordpress0, state.Alive) 2295 assertLife(c, logging0, state.Alive) 2296 assertLife(c, rel, state.Alive) 2297 } 2298 2299 func (s *serviceSuite) TestBlockDestroyDestroySubordinateUnits(c *gc.C) { 2300 wordpress := s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 2301 wordpress0, err := wordpress.AddUnit() 2302 c.Assert(err, jc.ErrorIsNil) 2303 s.AddTestingService(c, "logging", s.AddTestingCharm(c, "logging")) 2304 eps, err := s.State.InferEndpoints("logging", "wordpress") 2305 c.Assert(err, jc.ErrorIsNil) 2306 rel, err := s.State.AddRelation(eps...) 2307 c.Assert(err, jc.ErrorIsNil) 2308 ru, err := rel.Unit(wordpress0) 2309 c.Assert(err, jc.ErrorIsNil) 2310 err = ru.EnterScope(nil) 2311 c.Assert(err, jc.ErrorIsNil) 2312 logging0, err := s.State.Unit("logging/0") 2313 c.Assert(err, jc.ErrorIsNil) 2314 2315 s.BlockDestroyModel(c, "TestBlockDestroyDestroySubordinateUnits") 2316 // Try to destroy the subordinate alone; check it fails. 2317 err = s.applicationAPI.DestroyUnits(params.DestroyApplicationUnits{ 2318 UnitNames: []string{"logging/0"}, 2319 }) 2320 c.Assert(err, gc.ErrorMatches, `no units were destroyed: unit "logging/0" is a subordinate`) 2321 assertLife(c, logging0, state.Alive) 2322 2323 s.assertDestroySubordinateUnits(c, wordpress0, logging0) 2324 } 2325 2326 func (s *serviceSuite) TestClientSetServiceConstraints(c *gc.C) { 2327 application := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 2328 2329 // Update constraints for the application. 2330 cons, err := constraints.Parse("mem=4096", "cores=2") 2331 c.Assert(err, jc.ErrorIsNil) 2332 err = s.applicationAPI.SetConstraints(params.SetConstraints{ApplicationName: "dummy", Constraints: cons}) 2333 c.Assert(err, jc.ErrorIsNil) 2334 2335 // Ensure the constraints have been correctly updated. 2336 obtained, err := application.Constraints() 2337 c.Assert(err, jc.ErrorIsNil) 2338 c.Assert(obtained, gc.DeepEquals, cons) 2339 } 2340 2341 func (s *serviceSuite) setupSetServiceConstraints(c *gc.C) (*state.Application, constraints.Value) { 2342 application := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 2343 // Update constraints for the application. 2344 cons, err := constraints.Parse("mem=4096", "cores=2") 2345 c.Assert(err, jc.ErrorIsNil) 2346 return application, cons 2347 } 2348 2349 func (s *serviceSuite) assertSetServiceConstraints(c *gc.C, application *state.Application, cons constraints.Value) { 2350 err := s.applicationAPI.SetConstraints(params.SetConstraints{ApplicationName: "dummy", Constraints: cons}) 2351 c.Assert(err, jc.ErrorIsNil) 2352 // Ensure the constraints have been correctly updated. 2353 obtained, err := application.Constraints() 2354 c.Assert(err, jc.ErrorIsNil) 2355 c.Assert(obtained, gc.DeepEquals, cons) 2356 } 2357 2358 func (s *serviceSuite) assertSetServiceConstraintsBlocked(c *gc.C, msg string, service *state.Application, cons constraints.Value) { 2359 err := s.applicationAPI.SetConstraints(params.SetConstraints{ApplicationName: "dummy", Constraints: cons}) 2360 s.AssertBlocked(c, err, msg) 2361 } 2362 2363 func (s *serviceSuite) TestBlockDestroySetServiceConstraints(c *gc.C) { 2364 svc, cons := s.setupSetServiceConstraints(c) 2365 s.BlockDestroyModel(c, "TestBlockDestroySetServiceConstraints") 2366 s.assertSetServiceConstraints(c, svc, cons) 2367 } 2368 2369 func (s *serviceSuite) TestBlockRemoveSetServiceConstraints(c *gc.C) { 2370 svc, cons := s.setupSetServiceConstraints(c) 2371 s.BlockRemoveObject(c, "TestBlockRemoveSetServiceConstraints") 2372 s.assertSetServiceConstraints(c, svc, cons) 2373 } 2374 2375 func (s *serviceSuite) TestBlockChangesSetServiceConstraints(c *gc.C) { 2376 svc, cons := s.setupSetServiceConstraints(c) 2377 s.BlockAllChanges(c, "TestBlockChangesSetServiceConstraints") 2378 s.assertSetServiceConstraintsBlocked(c, "TestBlockChangesSetServiceConstraints", svc, cons) 2379 } 2380 2381 func (s *serviceSuite) TestClientGetServiceConstraints(c *gc.C) { 2382 application := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 2383 2384 // Set constraints for the application. 2385 cons, err := constraints.Parse("mem=4096", "cores=2") 2386 c.Assert(err, jc.ErrorIsNil) 2387 err = application.SetConstraints(cons) 2388 c.Assert(err, jc.ErrorIsNil) 2389 2390 // Check we can get the constraints. 2391 result, err := s.applicationAPI.GetConstraints(params.GetApplicationConstraints{"dummy"}) 2392 c.Assert(err, jc.ErrorIsNil) 2393 c.Assert(result.Constraints, gc.DeepEquals, cons) 2394 } 2395 2396 func (s *serviceSuite) checkEndpoints(c *gc.C, endpoints map[string]params.CharmRelation) { 2397 c.Assert(endpoints["wordpress"], gc.DeepEquals, params.CharmRelation{ 2398 Name: "db", 2399 Role: "requirer", 2400 Interface: "mysql", 2401 Optional: false, 2402 Limit: 1, 2403 Scope: "global", 2404 }) 2405 c.Assert(endpoints["mysql"], gc.DeepEquals, params.CharmRelation{ 2406 Name: "server", 2407 Role: "provider", 2408 Interface: "mysql", 2409 Optional: false, 2410 Limit: 0, 2411 Scope: "global", 2412 }) 2413 } 2414 2415 func (s *serviceSuite) setupRelationScenario(c *gc.C) { 2416 s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 2417 s.AddTestingService(c, "logging", s.AddTestingCharm(c, "logging")) 2418 eps, err := s.State.InferEndpoints("logging", "wordpress") 2419 c.Assert(err, jc.ErrorIsNil) 2420 _, err = s.State.AddRelation(eps...) 2421 c.Assert(err, jc.ErrorIsNil) 2422 } 2423 2424 func (s *serviceSuite) assertAddRelation(c *gc.C, endpoints []string) { 2425 s.setupRelationScenario(c) 2426 res, err := s.applicationAPI.AddRelation(params.AddRelation{Endpoints: endpoints}) 2427 c.Assert(err, jc.ErrorIsNil) 2428 s.checkEndpoints(c, res.Endpoints) 2429 // Show that the relation was added. 2430 wpSvc, err := s.State.Application("wordpress") 2431 c.Assert(err, jc.ErrorIsNil) 2432 rels, err := wpSvc.Relations() 2433 // There are 2 relations - the logging-wordpress one set up in the 2434 // scenario and the one created in this test. 2435 c.Assert(len(rels), gc.Equals, 2) 2436 mySvc, err := s.State.Application("mysql") 2437 c.Assert(err, jc.ErrorIsNil) 2438 rels, err = mySvc.Relations() 2439 c.Assert(err, jc.ErrorIsNil) 2440 c.Assert(len(rels), gc.Equals, 1) 2441 } 2442 2443 func (s *serviceSuite) TestSuccessfullyAddRelation(c *gc.C) { 2444 endpoints := []string{"wordpress", "mysql"} 2445 s.assertAddRelation(c, endpoints) 2446 } 2447 2448 func (s *serviceSuite) TestBlockDestroyAddRelation(c *gc.C) { 2449 s.BlockDestroyModel(c, "TestBlockDestroyAddRelation") 2450 s.assertAddRelation(c, []string{"wordpress", "mysql"}) 2451 } 2452 func (s *serviceSuite) TestBlockRemoveAddRelation(c *gc.C) { 2453 s.BlockRemoveObject(c, "TestBlockRemoveAddRelation") 2454 s.assertAddRelation(c, []string{"wordpress", "mysql"}) 2455 } 2456 2457 func (s *serviceSuite) TestBlockChangesAddRelation(c *gc.C) { 2458 s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 2459 s.BlockAllChanges(c, "TestBlockChangesAddRelation") 2460 _, err := s.applicationAPI.AddRelation(params.AddRelation{Endpoints: []string{"wordpress", "mysql"}}) 2461 s.AssertBlocked(c, err, "TestBlockChangesAddRelation") 2462 } 2463 2464 func (s *serviceSuite) TestSuccessfullyAddRelationSwapped(c *gc.C) { 2465 // Show that the order of the services listed in the AddRelation call 2466 // does not matter. This is a repeat of the previous test with the service 2467 // names swapped. 2468 endpoints := []string{"mysql", "wordpress"} 2469 s.assertAddRelation(c, endpoints) 2470 } 2471 2472 func (s *serviceSuite) TestCallWithOnlyOneEndpoint(c *gc.C) { 2473 s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 2474 endpoints := []string{"wordpress"} 2475 _, err := s.applicationAPI.AddRelation(params.AddRelation{Endpoints: endpoints}) 2476 c.Assert(err, gc.ErrorMatches, "no relations found") 2477 } 2478 2479 func (s *serviceSuite) TestCallWithOneEndpointTooMany(c *gc.C) { 2480 s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 2481 s.AddTestingService(c, "logging", s.AddTestingCharm(c, "logging")) 2482 endpoints := []string{"wordpress", "mysql", "logging"} 2483 _, err := s.applicationAPI.AddRelation(params.AddRelation{Endpoints: endpoints}) 2484 c.Assert(err, gc.ErrorMatches, "cannot relate 3 endpoints") 2485 } 2486 2487 func (s *serviceSuite) TestAddAlreadyAddedRelation(c *gc.C) { 2488 s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 2489 // Add a relation between wordpress and mysql. 2490 endpoints := []string{"wordpress", "mysql"} 2491 eps, err := s.State.InferEndpoints(endpoints...) 2492 c.Assert(err, jc.ErrorIsNil) 2493 _, err = s.State.AddRelation(eps...) 2494 c.Assert(err, jc.ErrorIsNil) 2495 // And try to add it again. 2496 _, err = s.applicationAPI.AddRelation(params.AddRelation{Endpoints: endpoints}) 2497 c.Assert(err, gc.ErrorMatches, `cannot add relation "wordpress:db mysql:server": relation already exists`) 2498 } 2499 2500 func (s *serviceSuite) setupDestroyRelationScenario(c *gc.C, endpoints []string) *state.Relation { 2501 s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 2502 // Add a relation between the endpoints. 2503 eps, err := s.State.InferEndpoints(endpoints...) 2504 c.Assert(err, jc.ErrorIsNil) 2505 relation, err := s.State.AddRelation(eps...) 2506 c.Assert(err, jc.ErrorIsNil) 2507 return relation 2508 } 2509 2510 func (s *serviceSuite) assertDestroyRelation(c *gc.C, endpoints []string) { 2511 s.assertDestroyRelationSuccess( 2512 c, 2513 s.setupDestroyRelationScenario(c, endpoints), 2514 endpoints) 2515 } 2516 2517 func (s *serviceSuite) assertDestroyRelationSuccess(c *gc.C, relation *state.Relation, endpoints []string) { 2518 err := s.applicationAPI.DestroyRelation(params.DestroyRelation{Endpoints: endpoints}) 2519 c.Assert(err, jc.ErrorIsNil) 2520 // Show that the relation was removed. 2521 c.Assert(relation.Refresh(), jc.Satisfies, errors.IsNotFound) 2522 } 2523 2524 func (s *serviceSuite) TestSuccessfulDestroyRelation(c *gc.C) { 2525 endpoints := []string{"wordpress", "mysql"} 2526 s.assertDestroyRelation(c, endpoints) 2527 } 2528 2529 func (s *serviceSuite) TestSuccessfullyDestroyRelationSwapped(c *gc.C) { 2530 // Show that the order of the services listed in the DestroyRelation call 2531 // does not matter. This is a repeat of the previous test with the service 2532 // names swapped. 2533 endpoints := []string{"mysql", "wordpress"} 2534 s.assertDestroyRelation(c, endpoints) 2535 } 2536 2537 func (s *serviceSuite) TestNoRelation(c *gc.C) { 2538 s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 2539 endpoints := []string{"wordpress", "mysql"} 2540 err := s.applicationAPI.DestroyRelation(params.DestroyRelation{Endpoints: endpoints}) 2541 c.Assert(err, gc.ErrorMatches, `relation "wordpress:db mysql:server" not found`) 2542 } 2543 2544 func (s *serviceSuite) TestAttemptDestroyingNonExistentRelation(c *gc.C) { 2545 s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 2546 s.AddTestingService(c, "riak", s.AddTestingCharm(c, "riak")) 2547 endpoints := []string{"riak", "wordpress"} 2548 err := s.applicationAPI.DestroyRelation(params.DestroyRelation{Endpoints: endpoints}) 2549 c.Assert(err, gc.ErrorMatches, "no relations found") 2550 } 2551 2552 func (s *serviceSuite) TestAttemptDestroyingWithOnlyOneEndpoint(c *gc.C) { 2553 s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 2554 endpoints := []string{"wordpress"} 2555 err := s.applicationAPI.DestroyRelation(params.DestroyRelation{Endpoints: endpoints}) 2556 c.Assert(err, gc.ErrorMatches, "no relations found") 2557 } 2558 2559 func (s *serviceSuite) TestAttemptDestroyingPeerRelation(c *gc.C) { 2560 s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 2561 s.AddTestingService(c, "riak", s.AddTestingCharm(c, "riak")) 2562 2563 endpoints := []string{"riak:ring"} 2564 err := s.applicationAPI.DestroyRelation(params.DestroyRelation{Endpoints: endpoints}) 2565 c.Assert(err, gc.ErrorMatches, `cannot destroy relation "riak:ring": is a peer relation`) 2566 } 2567 2568 func (s *serviceSuite) TestAttemptDestroyingAlreadyDestroyedRelation(c *gc.C) { 2569 s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 2570 2571 // Add a relation between wordpress and mysql. 2572 eps, err := s.State.InferEndpoints("wordpress", "mysql") 2573 c.Assert(err, jc.ErrorIsNil) 2574 rel, err := s.State.AddRelation(eps...) 2575 c.Assert(err, jc.ErrorIsNil) 2576 2577 endpoints := []string{"wordpress", "mysql"} 2578 err = s.applicationAPI.DestroyRelation(params.DestroyRelation{Endpoints: endpoints}) 2579 // Show that the relation was removed. 2580 c.Assert(rel.Refresh(), jc.Satisfies, errors.IsNotFound) 2581 2582 // And try to destroy it again. 2583 err = s.applicationAPI.DestroyRelation(params.DestroyRelation{Endpoints: endpoints}) 2584 c.Assert(err, gc.ErrorMatches, `relation "wordpress:db mysql:server" not found`) 2585 } 2586 2587 func (s *serviceSuite) TestBlockRemoveDestroyRelation(c *gc.C) { 2588 endpoints := []string{"wordpress", "mysql"} 2589 relation := s.setupDestroyRelationScenario(c, endpoints) 2590 // block remove-objects 2591 s.BlockRemoveObject(c, "TestBlockRemoveDestroyRelation") 2592 err := s.applicationAPI.DestroyRelation(params.DestroyRelation{Endpoints: endpoints}) 2593 s.AssertBlocked(c, err, "TestBlockRemoveDestroyRelation") 2594 assertLife(c, relation, state.Alive) 2595 } 2596 2597 func (s *serviceSuite) TestBlockChangeDestroyRelation(c *gc.C) { 2598 endpoints := []string{"wordpress", "mysql"} 2599 relation := s.setupDestroyRelationScenario(c, endpoints) 2600 s.BlockAllChanges(c, "TestBlockChangeDestroyRelation") 2601 err := s.applicationAPI.DestroyRelation(params.DestroyRelation{Endpoints: endpoints}) 2602 s.AssertBlocked(c, err, "TestBlockChangeDestroyRelation") 2603 assertLife(c, relation, state.Alive) 2604 } 2605 2606 func (s *serviceSuite) TestBlockDestroyDestroyRelation(c *gc.C) { 2607 s.BlockDestroyModel(c, "TestBlockDestroyDestroyRelation") 2608 endpoints := []string{"wordpress", "mysql"} 2609 s.assertDestroyRelation(c, endpoints) 2610 } 2611 2612 type mockStorageProvider struct { 2613 storage.Provider 2614 kind storage.StorageKind 2615 } 2616 2617 func (m *mockStorageProvider) Scope() storage.Scope { 2618 return storage.ScopeMachine 2619 } 2620 2621 func (m *mockStorageProvider) Supports(k storage.StorageKind) bool { 2622 return k == m.kind 2623 } 2624 2625 func (m *mockStorageProvider) ValidateConfig(*storage.Config) error { 2626 return nil 2627 } 2628 2629 type blobs struct { 2630 sync.Mutex 2631 m map[string]bool // maps path to added (true), or deleted (false) 2632 } 2633 2634 // Add adds a path to the list of known paths. 2635 func (b *blobs) Add(path string) { 2636 b.Lock() 2637 defer b.Unlock() 2638 b.check() 2639 b.m[path] = true 2640 } 2641 2642 // Remove marks a path as deleted, even if it was not previously Added. 2643 func (b *blobs) Remove(path string) { 2644 b.Lock() 2645 defer b.Unlock() 2646 b.check() 2647 b.m[path] = false 2648 } 2649 2650 func (b *blobs) check() { 2651 if b.m == nil { 2652 b.m = make(map[string]bool) 2653 } 2654 } 2655 2656 type recordingStorage struct { 2657 statestorage.Storage 2658 putBarrier *sync.WaitGroup 2659 blobs *blobs 2660 } 2661 2662 func (s *recordingStorage) Put(path string, r io.Reader, size int64) error { 2663 if s.putBarrier != nil { 2664 // This goroutine has gotten to Put() so mark it Done() and 2665 // wait for the other goroutines to get to this point. 2666 s.putBarrier.Done() 2667 s.putBarrier.Wait() 2668 } 2669 if err := s.Storage.Put(path, r, size); err != nil { 2670 return errors.Trace(err) 2671 } 2672 s.blobs.Add(path) 2673 return nil 2674 } 2675 2676 func (s *recordingStorage) Remove(path string) error { 2677 if err := s.Storage.Remove(path); err != nil { 2678 return errors.Trace(err) 2679 } 2680 s.blobs.Remove(path) 2681 return nil 2682 }