github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/apiserver/facades/client/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/ioutil" 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" 18 charmresource "gopkg.in/juju/charm.v6/resource" 19 "gopkg.in/juju/charmrepo.v3" 20 "gopkg.in/juju/charmrepo.v3/csclient" 21 csparams "gopkg.in/juju/charmrepo.v3/csclient/params" 22 "gopkg.in/juju/names.v2" 23 "gopkg.in/macaroon.v2-unstable" 24 "gopkg.in/mgo.v2" 25 26 "github.com/juju/juju/apiserver/common" 27 commontesting "github.com/juju/juju/apiserver/common/testing" 28 "github.com/juju/juju/apiserver/facades/client/application" 29 "github.com/juju/juju/apiserver/params" 30 apiservertesting "github.com/juju/juju/apiserver/testing" 31 "github.com/juju/juju/core/constraints" 32 "github.com/juju/juju/core/instance" 33 "github.com/juju/juju/core/model" 34 "github.com/juju/juju/core/status" 35 jujutesting "github.com/juju/juju/juju/testing" 36 "github.com/juju/juju/state" 37 "github.com/juju/juju/state/stateenvirons" 38 statestorage "github.com/juju/juju/state/storage" 39 statetesting "github.com/juju/juju/state/testing" 40 "github.com/juju/juju/storage" 41 "github.com/juju/juju/storage/poolmanager" 42 "github.com/juju/juju/testcharms" 43 "github.com/juju/juju/testing" 44 "github.com/juju/juju/testing/factory" 45 jujuversion "github.com/juju/juju/version" 46 ) 47 48 type applicationSuite struct { 49 jujutesting.JujuConnSuite 50 apiservertesting.CharmStoreSuite 51 commontesting.BlockHelper 52 53 applicationAPI *application.APIv9 54 application *state.Application 55 authorizer *apiservertesting.FakeAuthorizer 56 } 57 58 var _ = gc.Suite(&applicationSuite{}) 59 60 func (s *applicationSuite) SetUpSuite(c *gc.C) { 61 s.CharmStoreSuite.SetUpSuite(c) 62 s.JujuConnSuite.SetUpSuite(c) 63 } 64 65 func (s *applicationSuite) TearDownSuite(c *gc.C) { 66 s.CharmStoreSuite.TearDownSuite(c) 67 s.JujuConnSuite.TearDownSuite(c) 68 } 69 70 func (s *applicationSuite) SetUpTest(c *gc.C) { 71 s.JujuConnSuite.SetUpTest(c) 72 s.CharmStoreSuite.Session = s.JujuConnSuite.Session 73 s.CharmStoreSuite.SetUpTest(c) 74 s.BlockHelper = commontesting.NewBlockHelper(s.APIState) 75 s.AddCleanup(func(*gc.C) { s.BlockHelper.Close() }) 76 77 s.application = s.Factory.MakeApplication(c, nil) 78 79 s.authorizer = &apiservertesting.FakeAuthorizer{ 80 Tag: s.AdminUserTag(c), 81 } 82 s.applicationAPI = s.makeAPI(c) 83 } 84 85 func (s *applicationSuite) TearDownTest(c *gc.C) { 86 s.CharmStoreSuite.TearDownTest(c) 87 s.JujuConnSuite.TearDownTest(c) 88 } 89 90 func (s *applicationSuite) makeAPI(c *gc.C) *application.APIv9 { 91 resources := common.NewResources() 92 c.Assert(resources.RegisterNamed("dataDir", common.StringResource(c.MkDir())), jc.ErrorIsNil) 93 storageAccess, err := application.GetStorageState(s.State) 94 c.Assert(err, jc.ErrorIsNil) 95 model, err := s.State.Model() 96 c.Assert(err, jc.ErrorIsNil) 97 blockChecker := common.NewBlockChecker(s.State) 98 registry := stateenvirons.NewStorageProviderRegistry(s.Environ) 99 pm := poolmanager.New(state.NewStateSettings(s.State), registry) 100 api, err := application.NewAPIBase( 101 application.GetState(s.State), 102 storageAccess, 103 s.authorizer, 104 blockChecker, 105 model.ModelTag(), 106 model.Type(), 107 model.Name(), 108 application.CharmToStateCharm, 109 application.DeployApplication, 110 pm, 111 common.NewResources(), 112 nil, // CAAS Broker not used in this suite. 113 ) 114 c.Assert(err, jc.ErrorIsNil) 115 return &application.APIv9{api} 116 } 117 118 func (s *applicationSuite) TestCharmConfig(c *gc.C) { 119 s.setUpConfigTest(c) 120 121 // TODO (manadart 2019-02-04): When upstream methods receive a generation, 122 // refactor these tests to account for it. 123 results, err := s.applicationAPI.CharmConfig(params.ApplicationGetArgs{ 124 Args: []params.ApplicationGet{ 125 {ApplicationName: "foo", Generation: model.GenerationCurrent}, 126 {ApplicationName: "bar", Generation: model.GenerationCurrent}, 127 {ApplicationName: "wat", Generation: model.GenerationCurrent}, 128 }, 129 }) 130 assertConfigTest(c, results, err, []params.ConfigResult{}) 131 } 132 133 func (s *applicationSuite) TestCharmConfigV8(c *gc.C) { 134 s.setUpConfigTest(c) 135 api := &application.APIv8{APIv9: s.applicationAPI} 136 results, err := api.CharmConfig(params.Entities{ 137 Entities: []params.Entity{ 138 {"wat"}, {"machine-0"}, {"user-foo"}, 139 {"application-foo"}, {"application-bar"}, {"application-wat"}, 140 }, 141 }) 142 assertConfigTest(c, results, err, []params.ConfigResult{ 143 {Error: ¶ms.Error{Message: `"wat" is not a valid tag`}}, 144 {Error: ¶ms.Error{Message: `unexpected tag type, expected application, got machine`}}, 145 {Error: ¶ms.Error{Message: `unexpected tag type, expected application, got user`}}, 146 }) 147 } 148 149 func (s *applicationSuite) TestGetConfig(c *gc.C) { 150 s.setUpConfigTest(c) 151 results, err := s.applicationAPI.GetConfig(params.Entities{ 152 Entities: []params.Entity{ 153 {"wat"}, {"machine-0"}, {"user-foo"}, 154 {"application-foo"}, {"application-bar"}, {"application-wat"}, 155 }, 156 }) 157 assertConfigTest(c, results, err, []params.ConfigResult{ 158 {Error: ¶ms.Error{Message: `"wat" is not a valid tag`}}, 159 {Error: ¶ms.Error{Message: `unexpected tag type, expected application, got machine`}}, 160 {Error: ¶ms.Error{Message: `unexpected tag type, expected application, got user`}}, 161 }) 162 } 163 164 func (s *applicationSuite) setUpConfigTest(c *gc.C) { 165 fooConfig := map[string]interface{}{ 166 "title": "foo", 167 "skill-level": 42, 168 } 169 dummy := s.Factory.MakeCharm(c, &factory.CharmParams{ 170 Name: "dummy", 171 }) 172 s.Factory.MakeApplication(c, &factory.ApplicationParams{ 173 Name: "foo", 174 Charm: dummy, 175 CharmConfig: fooConfig, 176 }) 177 barConfig := map[string]interface{}{ 178 "title": "bar", 179 "outlook": "fantastic", 180 } 181 s.Factory.MakeApplication(c, &factory.ApplicationParams{ 182 Name: "bar", 183 Charm: dummy, 184 CharmConfig: barConfig, 185 }) 186 } 187 188 func assertConfigTest(c *gc.C, results params.ApplicationGetConfigResults, err error, resPrefix []params.ConfigResult) { 189 c.Assert(err, jc.ErrorIsNil) 190 c.Assert(results, jc.DeepEquals, params.ApplicationGetConfigResults{ 191 Results: append(resPrefix, []params.ConfigResult{ 192 { 193 Config: map[string]interface{}{ 194 "outlook": map[string]interface{}{ 195 "description": "No default outlook.", 196 "source": "unset", 197 "type": "string", 198 }, 199 "skill-level": map[string]interface{}{ 200 "description": "A number indicating skill.", 201 "source": "user", 202 "type": "int", 203 "value": 42, 204 }, 205 "title": map[string]interface{}{ 206 "default": "My Title", 207 "description": "A descriptive title used for the application.", 208 "source": "user", 209 "type": "string", 210 "value": "foo", 211 }, 212 "username": map[string]interface{}{ 213 "default": "admin001", 214 "description": "The name of the initial account (given admin permissions).", 215 "source": "default", 216 "type": "string", 217 "value": "admin001", 218 }, 219 }, 220 }, { 221 Config: map[string]interface{}{ 222 "outlook": map[string]interface{}{ 223 "description": "No default outlook.", 224 "source": "user", 225 "type": "string", 226 "value": "fantastic", 227 }, 228 "skill-level": map[string]interface{}{ 229 "description": "A number indicating skill.", 230 "source": "unset", 231 "type": "int", 232 }, 233 "title": map[string]interface{}{ 234 "default": "My Title", 235 "description": "A descriptive title used for the application.", 236 "source": "user", 237 "type": "string", 238 "value": "bar", 239 }, 240 "username": map[string]interface{}{ 241 "default": "admin001", 242 "description": "The name of the initial account (given admin permissions).", 243 "source": "default", 244 "type": "string", 245 "value": "admin001", 246 }, 247 }, 248 }, { 249 Error: ¶ms.Error{Message: `application "wat" not found`, Code: "not found"}, 250 }, 251 }...)}) 252 } 253 254 func (s *applicationSuite) TestSetMetricCredentials(c *gc.C) { 255 ch := s.Factory.MakeCharm(c, &factory.CharmParams{Name: "wordpress"}) 256 wordpress := s.Factory.MakeApplication(c, &factory.ApplicationParams{ 257 Charm: ch, 258 }) 259 tests := []struct { 260 about string 261 args params.ApplicationMetricCredentials 262 results params.ErrorResults 263 }{ 264 { 265 "test one argument and it passes", 266 params.ApplicationMetricCredentials{Creds: []params.ApplicationMetricCredential{{ 267 ApplicationName: s.application.Name(), 268 MetricCredentials: []byte("creds 1234"), 269 }}}, 270 params.ErrorResults{Results: []params.ErrorResult{{Error: nil}}}, 271 }, 272 { 273 "test two arguments and both pass", 274 params.ApplicationMetricCredentials{Creds: []params.ApplicationMetricCredential{ 275 { 276 ApplicationName: s.application.Name(), 277 MetricCredentials: []byte("creds 1234"), 278 }, 279 { 280 ApplicationName: wordpress.Name(), 281 MetricCredentials: []byte("creds 4567"), 282 }, 283 }}, 284 params.ErrorResults{Results: []params.ErrorResult{ 285 {Error: nil}, 286 {Error: nil}, 287 }}, 288 }, 289 { 290 "test two arguments and second one fails", 291 params.ApplicationMetricCredentials{Creds: []params.ApplicationMetricCredential{ 292 { 293 ApplicationName: s.application.Name(), 294 MetricCredentials: []byte("creds 1234"), 295 }, 296 { 297 ApplicationName: "not-a-application", 298 MetricCredentials: []byte("creds 4567"), 299 }, 300 }}, 301 params.ErrorResults{Results: []params.ErrorResult{ 302 {Error: nil}, 303 {Error: ¶ms.Error{Message: `application "not-a-application" not found`, Code: "not found"}}, 304 }}, 305 }, 306 } 307 for i, t := range tests { 308 c.Logf("Running test %d %v", i, t.about) 309 results, err := s.applicationAPI.SetMetricCredentials(t.args) 310 c.Assert(err, jc.ErrorIsNil) 311 c.Assert(results.Results, gc.HasLen, len(t.results.Results)) 312 c.Assert(results, gc.DeepEquals, t.results) 313 314 for i, a := range t.args.Creds { 315 if t.results.Results[i].Error == nil { 316 app, err := s.State.Application(a.ApplicationName) 317 c.Assert(err, jc.ErrorIsNil) 318 creds := app.MetricCredentials() 319 c.Assert(creds, gc.DeepEquals, a.MetricCredentials) 320 } 321 } 322 } 323 } 324 325 func (s *applicationSuite) TestCompatibleSettingsParsing(c *gc.C) { 326 // Test the exported settings parsing in a compatible way. 327 s.AddTestingApplication(c, "dummy", s.AddTestingCharm(c, "dummy")) 328 app, err := s.State.Application("dummy") 329 c.Assert(err, jc.ErrorIsNil) 330 ch, _, err := app.Charm() 331 c.Assert(err, jc.ErrorIsNil) 332 c.Assert(ch.URL().String(), gc.Equals, "local:quantal/dummy-1") 333 334 // Empty string will be returned as nil. 335 options := map[string]string{ 336 "title": "foobar", 337 "username": "", 338 } 339 settings, err := application.ParseSettingsCompatible(ch.Config(), options) 340 c.Assert(err, jc.ErrorIsNil) 341 c.Assert(settings, gc.DeepEquals, charm.Settings{ 342 "title": "foobar", 343 "username": nil, 344 }) 345 346 // Illegal settings lead to an error. 347 options = map[string]string{ 348 "yummy": "didgeridoo", 349 } 350 _, err = application.ParseSettingsCompatible(ch.Config(), options) 351 c.Assert(err, gc.ErrorMatches, `unknown option "yummy"`) 352 } 353 354 func (s *applicationSuite) TestApplicationDeployWithStorage(c *gc.C) { 355 curl, ch := s.UploadCharm(c, "utopic/storage-block-10", "storage-block") 356 err := application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{ 357 URL: curl.String(), 358 }) 359 c.Assert(err, jc.ErrorIsNil) 360 storageConstraints := map[string]storage.Constraints{ 361 "data": { 362 Count: 1, 363 Size: 1024, 364 Pool: "modelscoped-block", 365 }, 366 } 367 368 var cons constraints.Value 369 args := params.ApplicationDeploy{ 370 ApplicationName: "application", 371 CharmURL: curl.String(), 372 NumUnits: 1, 373 Constraints: cons, 374 Storage: storageConstraints, 375 } 376 results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{ 377 Applications: []params.ApplicationDeploy{args}}, 378 ) 379 c.Assert(err, jc.ErrorIsNil) 380 c.Assert(results, gc.DeepEquals, params.ErrorResults{ 381 Results: []params.ErrorResult{{Error: nil}}, 382 }) 383 app := apiservertesting.AssertPrincipalApplicationDeployed(c, s.State, "application", curl, false, ch, cons) 384 storageConstraintsOut, err := app.StorageConstraints() 385 c.Assert(err, jc.ErrorIsNil) 386 c.Assert(storageConstraintsOut, gc.DeepEquals, map[string]state.StorageConstraints{ 387 "data": { 388 Count: 1, 389 Size: 1024, 390 Pool: "modelscoped-block", 391 }, 392 "allecto": { 393 Count: 0, 394 Size: 1024, 395 Pool: "loop", 396 }, 397 }) 398 } 399 400 func (s *applicationSuite) TestMinJujuVersionTooHigh(c *gc.C) { 401 curl, _ := s.UploadCharm(c, "quantal/minjujuversion-0", "minjujuversion") 402 err := application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{ 403 URL: curl.String(), 404 }) 405 match := fmt.Sprintf(`charm's min version (999.999.999) is higher than this juju model's version (%s)`, jujuversion.Current) 406 c.Assert(err, gc.ErrorMatches, regexp.QuoteMeta(match)) 407 } 408 409 func (s *applicationSuite) TestApplicationDeployWithInvalidStoragePool(c *gc.C) { 410 curl, _ := s.UploadCharm(c, "utopic/storage-block-0", "storage-block") 411 err := application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{ 412 URL: curl.String(), 413 }) 414 c.Assert(err, jc.ErrorIsNil) 415 storageConstraints := map[string]storage.Constraints{ 416 "data": { 417 Pool: "foo", 418 Count: 1, 419 Size: 1024, 420 }, 421 } 422 423 var cons constraints.Value 424 args := params.ApplicationDeploy{ 425 ApplicationName: "application", 426 CharmURL: curl.String(), 427 NumUnits: 1, 428 Constraints: cons, 429 Storage: storageConstraints, 430 } 431 results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{ 432 Applications: []params.ApplicationDeploy{args}}, 433 ) 434 c.Assert(err, jc.ErrorIsNil) 435 c.Assert(results.Results, gc.HasLen, 1) 436 c.Assert(results.Results[0].Error, gc.ErrorMatches, `.* pool "foo" not found`) 437 } 438 439 func (s *applicationSuite) TestApplicationDeployDefaultFilesystemStorage(c *gc.C) { 440 curl, ch := s.UploadCharm(c, "trusty/storage-filesystem-1", "storage-filesystem") 441 err := application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{ 442 URL: curl.String(), 443 }) 444 c.Assert(err, jc.ErrorIsNil) 445 var cons constraints.Value 446 args := params.ApplicationDeploy{ 447 ApplicationName: "application", 448 CharmURL: curl.String(), 449 NumUnits: 1, 450 Constraints: cons, 451 } 452 results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{ 453 Applications: []params.ApplicationDeploy{args}}, 454 ) 455 c.Assert(err, jc.ErrorIsNil) 456 c.Assert(results, gc.DeepEquals, params.ErrorResults{ 457 Results: []params.ErrorResult{{Error: nil}}, 458 }) 459 app := apiservertesting.AssertPrincipalApplicationDeployed(c, s.State, "application", curl, false, ch, cons) 460 storageConstraintsOut, err := app.StorageConstraints() 461 c.Assert(err, jc.ErrorIsNil) 462 c.Assert(storageConstraintsOut, gc.DeepEquals, map[string]state.StorageConstraints{ 463 "data": { 464 Count: 1, 465 Size: 1024, 466 Pool: "rootfs", 467 }, 468 }) 469 } 470 471 func (s *applicationSuite) TestApplicationDeploy(c *gc.C) { 472 curl, ch := s.UploadCharm(c, "precise/dummy-42", "dummy") 473 err := application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{ 474 URL: curl.String(), 475 }) 476 c.Assert(err, jc.ErrorIsNil) 477 var cons constraints.Value 478 args := params.ApplicationDeploy{ 479 ApplicationName: "application", 480 CharmURL: curl.String(), 481 NumUnits: 1, 482 Constraints: cons, 483 Placement: []*instance.Placement{ 484 {"deadbeef-0bad-400d-8000-4b1d0d06f00d", "valid"}, 485 }, 486 } 487 results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{ 488 Applications: []params.ApplicationDeploy{args}}, 489 ) 490 c.Assert(err, jc.ErrorIsNil) 491 c.Assert(results, gc.DeepEquals, params.ErrorResults{ 492 Results: []params.ErrorResult{{Error: nil}}, 493 }) 494 app := apiservertesting.AssertPrincipalApplicationDeployed(c, s.State, "application", curl, false, ch, cons) 495 units, err := app.AllUnits() 496 c.Assert(err, jc.ErrorIsNil) 497 c.Assert(units, gc.HasLen, 1) 498 499 // Check that the charm cache dir is cleared out. 500 files, err := ioutil.ReadDir(charmrepo.CacheDir) 501 c.Assert(err, jc.ErrorIsNil) 502 c.Assert(files, gc.HasLen, 0) 503 } 504 505 func (s *applicationSuite) TestApplicationDeployWithInvalidPlacement(c *gc.C) { 506 curl, _ := s.UploadCharm(c, "precise/dummy-42", "dummy") 507 err := application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{ 508 URL: curl.String(), 509 }) 510 c.Assert(err, jc.ErrorIsNil) 511 var cons constraints.Value 512 args := params.ApplicationDeploy{ 513 ApplicationName: "application", 514 CharmURL: curl.String(), 515 NumUnits: 1, 516 Constraints: cons, 517 Placement: []*instance.Placement{ 518 {"deadbeef-0bad-400d-8000-4b1d0d06f00d", "invalid"}, 519 }, 520 } 521 results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{ 522 Applications: []params.ApplicationDeploy{args}}, 523 ) 524 c.Assert(err, jc.ErrorIsNil) 525 c.Assert(results.Results, gc.HasLen, 1) 526 c.Assert(results.Results[0].Error, gc.NotNil) 527 c.Assert(results.Results[0].Error.Error(), gc.Matches, ".* invalid placement is invalid") 528 } 529 530 func (s *applicationSuite) TestApplicationDeployWithMachinePlacementLockedError(c *gc.C) { 531 s.testApplicationDeployWithPlacementLockedError(c, instance.Placement{"#", "0"}, false) 532 } 533 534 func (s *applicationSuite) TestApplicationDeployWithMachineContainerPlacementLockedError(c *gc.C) { 535 s.testApplicationDeployWithPlacementLockedError(c, instance.Placement{"lxd", "0"}, false) 536 } 537 538 func (s *applicationSuite) TestApplicationDeployWithExtantMachineContainerLockedParentError(c *gc.C) { 539 s.testApplicationDeployWithPlacementLockedError(c, instance.Placement{"#", "0/lxd/0"}, true) 540 } 541 542 func (s *applicationSuite) testApplicationDeployWithPlacementLockedError( 543 c *gc.C, placement instance.Placement, addContainer bool, 544 ) { 545 m, err := s.BackingState.AddMachine("precise", state.JobHostUnits) 546 c.Assert(err, jc.ErrorIsNil) 547 548 if addContainer { 549 template := state.MachineTemplate{ 550 Series: "xenial", 551 Jobs: []state.MachineJob{state.JobHostUnits}, 552 } 553 _, err := s.State.AddMachineInsideMachine(template, m.Id(), "lxd") 554 c.Assert(err, jc.ErrorIsNil) 555 } 556 557 c.Assert(m.CreateUpgradeSeriesLock(nil, "trusty"), jc.ErrorIsNil) 558 559 curl, _ := s.UploadCharm(c, "precise/dummy-42", "dummy") 560 err = application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{ 561 URL: curl.String(), 562 }) 563 c.Assert(err, jc.ErrorIsNil) 564 var cons constraints.Value 565 args := params.ApplicationDeploy{ 566 ApplicationName: "application", 567 CharmURL: curl.String(), 568 NumUnits: 1, 569 Constraints: cons, 570 Placement: []*instance.Placement{&placement}, 571 } 572 results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{ 573 Applications: []params.ApplicationDeploy{args}}, 574 ) 575 c.Assert(err, jc.ErrorIsNil) 576 c.Assert(results.Results, gc.HasLen, 1) 577 c.Assert(results.Results[0].Error, gc.NotNil) 578 c.Assert(results.Results[0].Error.Error(), gc.Matches, ".* machine is locked for series upgrade") 579 } 580 581 func (s *applicationSuite) TestApplicationDeploymentRemovesPendingResourcesOnFailure(c *gc.C) { 582 charm := s.AddTestingCharm(c, "dummy-resource") 583 resources, err := s.State.Resources() 584 c.Assert(err, jc.ErrorIsNil) 585 pendingID, err := resources.AddPendingResource("haha/borken", "user", charmresource.Resource{ 586 Meta: charm.Meta().Resources["dummy"], 587 Origin: charmresource.OriginUpload, 588 }) 589 c.Assert(err, jc.ErrorIsNil) 590 591 results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{ 592 Applications: []params.ApplicationDeploy{{ 593 ApplicationName: "haha/borken", 594 NumUnits: 1, 595 CharmURL: charm.URL().String(), 596 Resources: map[string]string{"dummy": pendingID}, 597 }}, 598 }) 599 c.Assert(err, jc.ErrorIsNil) 600 c.Assert(results.Results, gc.HasLen, 1) 601 c.Assert(results.Results[0].Error, gc.ErrorMatches, `cannot add application "haha/borken": invalid name`) 602 603 res, err := resources.ListPendingResources("haha/borken") 604 c.Assert(err, jc.ErrorIsNil) 605 c.Assert(res, gc.HasLen, 0) 606 } 607 608 func (s *applicationSuite) TestApplicationDeploymentLeavesResourcesOnSuccess(c *gc.C) { 609 charm := s.AddTestingCharm(c, "dummy-resource") 610 resources, err := s.State.Resources() 611 c.Assert(err, jc.ErrorIsNil) 612 pendingID, err := resources.AddPendingResource("unborken", "user", charmresource.Resource{ 613 Meta: charm.Meta().Resources["dummy"], 614 Origin: charmresource.OriginUpload, 615 }) 616 c.Assert(err, jc.ErrorIsNil) 617 618 results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{ 619 Applications: []params.ApplicationDeploy{{ 620 ApplicationName: "unborken", 621 NumUnits: 1, 622 CharmURL: charm.URL().String(), 623 Resources: map[string]string{"dummy": pendingID}, 624 }}, 625 }) 626 c.Assert(err, jc.ErrorIsNil) 627 c.Assert(results.Results, gc.HasLen, 1) 628 c.Assert(results.Results[0].Error, gc.IsNil) 629 630 res, err := resources.ListResources("unborken") 631 c.Assert(err, jc.ErrorIsNil) 632 c.Assert(res.Resources, gc.HasLen, 1) 633 } 634 635 func (s *applicationSuite) TestApplicationDeploymentWithTrust(c *gc.C) { 636 // This test should fail if the configuration parsing does not 637 // understand the "trust" configuration parameter 638 curl, ch := s.UploadCharm(c, "precise/dummy-42", "dummy") 639 err := application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{ 640 URL: curl.String(), 641 }) 642 c.Assert(err, jc.ErrorIsNil) 643 var cons constraints.Value 644 config := map[string]string{"trust": "true"} 645 args := params.ApplicationDeploy{ 646 ApplicationName: "application", 647 CharmURL: curl.String(), 648 NumUnits: 1, 649 Config: config, 650 Placement: []*instance.Placement{ 651 {"deadbeef-0bad-400d-8000-4b1d0d06f00d", "valid"}, 652 }, 653 } 654 results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{ 655 Applications: []params.ApplicationDeploy{args}}, 656 ) 657 c.Assert(err, jc.ErrorIsNil) 658 c.Assert(results, gc.DeepEquals, params.ErrorResults{ 659 Results: []params.ErrorResult{{Error: nil}}, 660 }) 661 662 app := apiservertesting.AssertPrincipalApplicationDeployed(c, s.State, "application", curl, false, ch, cons) 663 664 appConfig, err := app.ApplicationConfig() 665 c.Assert(err, jc.ErrorIsNil) 666 667 trust := appConfig.GetBool("trust", false) 668 c.Assert(trust, jc.IsTrue) 669 } 670 671 func (s *applicationSuite) TestApplicationDeploymentNoTrust(c *gc.C) { 672 // This test should fail if the trust configuration setting defaults to 673 // anything other than "false" when no configuration parameter for trust 674 // is set at deployment. 675 curl, ch := s.UploadCharm(c, "precise/dummy-42", "dummy") 676 err := application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{ 677 URL: curl.String(), 678 }) 679 c.Assert(err, jc.ErrorIsNil) 680 var cons constraints.Value 681 args := params.ApplicationDeploy{ 682 ApplicationName: "application", 683 CharmURL: curl.String(), 684 NumUnits: 1, 685 Placement: []*instance.Placement{ 686 {"deadbeef-0bad-400d-8000-4b1d0d06f00d", "valid"}, 687 }, 688 } 689 results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{ 690 Applications: []params.ApplicationDeploy{args}}, 691 ) 692 c.Assert(err, jc.ErrorIsNil) 693 c.Assert(results, gc.DeepEquals, params.ErrorResults{ 694 Results: []params.ErrorResult{{Error: nil}}, 695 }) 696 697 app := apiservertesting.AssertPrincipalApplicationDeployed(c, s.State, "application", curl, false, ch, cons) 698 appConfig, err := app.ApplicationConfig() 699 trust := appConfig.GetBool(application.TrustConfigOptionName, true) 700 c.Assert(trust, jc.IsFalse) 701 } 702 703 func (s *applicationSuite) testClientApplicationsDeployWithBindings(c *gc.C, endpointBindings, expected map[string]string) { 704 curl, _ := s.UploadCharm(c, "utopic/riak-42", "riak") 705 err := application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{ 706 URL: curl.String(), 707 }) 708 c.Assert(err, jc.ErrorIsNil) 709 710 var cons constraints.Value 711 args := params.ApplicationDeploy{ 712 ApplicationName: "application", 713 CharmURL: curl.String(), 714 NumUnits: 1, 715 Constraints: cons, 716 EndpointBindings: endpointBindings, 717 } 718 719 results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{ 720 Applications: []params.ApplicationDeploy{args}}, 721 ) 722 c.Assert(err, jc.ErrorIsNil) 723 c.Assert(results.Results, gc.HasLen, 1) 724 c.Assert(results.Results[0].Error, gc.IsNil) 725 726 app, err := s.State.Application(args.ApplicationName) 727 c.Assert(err, jc.ErrorIsNil) 728 729 retrievedBindings, err := app.EndpointBindings() 730 c.Assert(err, jc.ErrorIsNil) 731 c.Assert(retrievedBindings, jc.DeepEquals, expected) 732 } 733 734 func (s *applicationSuite) TestClientApplicationsDeployWithBindings(c *gc.C) { 735 s.State.AddSpace("a-space", "", nil, true) 736 expected := map[string]string{ 737 "endpoint": "a-space", 738 "ring": "", 739 "admin": "", 740 } 741 endpointBindings := map[string]string{"endpoint": "a-space"} 742 s.testClientApplicationsDeployWithBindings(c, endpointBindings, expected) 743 } 744 745 func (s *applicationSuite) TestClientApplicationsDeployWithDefaultBindings(c *gc.C) { 746 expected := map[string]string{ 747 "endpoint": "", 748 "ring": "", 749 "admin": "", 750 } 751 s.testClientApplicationsDeployWithBindings(c, nil, expected) 752 } 753 754 // TODO(wallyworld) - the following charm tests have been moved from the apiserver/client 755 // package in order to use the fake charm store testing infrastructure. They are legacy tests 756 // written to use the api client instead of the apiserver logic. They need to be rewritten and 757 // feature tests added. 758 759 func (s *applicationSuite) TestAddCharm(c *gc.C) { 760 var blobs blobs 761 s.PatchValue(application.NewStateStorage, func(uuid string, session *mgo.Session) statestorage.Storage { 762 storage := statestorage.NewStorage(uuid, session) 763 return &recordingStorage{Storage: storage, blobs: &blobs} 764 }) 765 766 client := s.APIState.Client() 767 // First test the sanity checks. 768 err := client.AddCharm(&charm.URL{Name: "nonsense"}, csparams.StableChannel, false) 769 c.Assert(err, gc.ErrorMatches, `cannot parse charm or bundle URL: ":nonsense-0"`) 770 err = client.AddCharm(charm.MustParseURL("local:precise/dummy"), csparams.StableChannel, false) 771 c.Assert(err, gc.ErrorMatches, "only charm store charm URLs are supported, with cs: schema") 772 err = client.AddCharm(charm.MustParseURL("cs:precise/wordpress"), csparams.StableChannel, false) 773 c.Assert(err, gc.ErrorMatches, "charm URL must include revision") 774 775 // Add a charm, without uploading it to storage, to 776 // check that AddCharm does not try to do it. 777 charmDir := testcharms.Repo.CharmDir("dummy") 778 ident := fmt.Sprintf("%s-%d", charmDir.Meta().Name, charmDir.Revision()) 779 curl := charm.MustParseURL("cs:quantal/" + ident) 780 info := state.CharmInfo{ 781 Charm: charmDir, 782 ID: curl, 783 StoragePath: "", 784 SHA256: ident + "-sha256", 785 } 786 sch, err := s.State.AddCharm(info) 787 c.Assert(err, jc.ErrorIsNil) 788 789 // AddCharm should see the charm in state and not upload it. 790 err = client.AddCharm(sch.URL(), csparams.StableChannel, false) 791 c.Assert(err, jc.ErrorIsNil) 792 793 c.Assert(blobs.m, gc.HasLen, 0) 794 795 // Now try adding another charm completely. 796 curl, _ = s.UploadCharm(c, "precise/wordpress-3", "wordpress") 797 err = client.AddCharm(curl, csparams.StableChannel, false) 798 c.Assert(err, jc.ErrorIsNil) 799 800 // Verify it's in state and it got uploaded. 801 storage := statestorage.NewStorage(s.State.ModelUUID(), s.State.MongoSession()) 802 sch, err = s.State.Charm(curl) 803 c.Assert(err, jc.ErrorIsNil) 804 s.assertUploaded(c, storage, sch.StoragePath(), sch.BundleSha256()) 805 } 806 807 func (s *applicationSuite) TestAddCharmWithAuthorization(c *gc.C) { 808 // Upload a new charm to the charm store. 809 curl, _ := s.UploadCharm(c, "cs:~restricted/precise/wordpress-3", "wordpress") 810 811 // Change permissions on the new charm such that only bob 812 // can read from it. 813 s.DischargeUser = "restricted" 814 err := s.Client.Put("/"+curl.Path()+"/meta/perm/read", []string{"bob"}) 815 c.Assert(err, jc.ErrorIsNil) 816 817 // Try to add a charm to the model without authorization. 818 s.DischargeUser = "" 819 err = s.APIState.Client().AddCharm(curl, csparams.StableChannel, false) 820 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\)`) 821 822 tryAs := func(user string) error { 823 client := csclient.New(csclient.Params{ 824 URL: s.Srv.URL, 825 }) 826 s.DischargeUser = user 827 var m *macaroon.Macaroon 828 err = client.Get("/delegatable-macaroon", &m) 829 c.Assert(err, gc.IsNil) 830 831 return application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{ 832 URL: curl.String(), 833 Channel: string(csparams.StableChannel), 834 }) 835 } 836 // Try again with authorization for the wrong user. 837 err = tryAs("joe") 838 c.Assert(err, gc.ErrorMatches, `cannot retrieve charm "cs:~restricted/precise/wordpress-3": cannot get archive: access denied for user "joe"`) 839 840 // Try again with the correct authorization this time. 841 err = tryAs("bob") 842 c.Assert(err, gc.IsNil) 843 844 // Verify that it has actually been uploaded. 845 _, err = s.State.Charm(curl) 846 c.Assert(err, gc.IsNil) 847 } 848 849 func (s *applicationSuite) TestAddCharmConcurrently(c *gc.C) { 850 c.Skip("see lp:1596960 -- bad test for bad code") 851 852 var putBarrier sync.WaitGroup 853 var blobs blobs 854 s.PatchValue(application.NewStateStorage, func(uuid string, session *mgo.Session) statestorage.Storage { 855 storage := statestorage.NewStorage(uuid, session) 856 return &recordingStorage{Storage: storage, blobs: &blobs, putBarrier: &putBarrier} 857 }) 858 859 client := s.APIState.Client() 860 curl, _ := s.UploadCharm(c, "trusty/wordpress-3", "wordpress") 861 862 // Try adding the same charm concurrently from multiple goroutines 863 // to test no "duplicate key errors" are reported (see lp bug 864 // #1067979) and also at the end only one charm document is 865 // created. 866 867 var wg sync.WaitGroup 868 // We don't add them 1-by-1 because that would allow each goroutine to 869 // finish separately without actually synchronizing between them 870 putBarrier.Add(10) 871 for i := 0; i < 10; i++ { 872 wg.Add(1) 873 go func(index int) { 874 defer wg.Done() 875 876 c.Assert(client.AddCharm(curl, csparams.StableChannel, false), gc.IsNil, gc.Commentf("goroutine %d", index)) 877 sch, err := s.State.Charm(curl) 878 c.Assert(err, gc.IsNil, gc.Commentf("goroutine %d", index)) 879 c.Assert(sch.URL(), jc.DeepEquals, curl, gc.Commentf("goroutine %d", index)) 880 }(i) 881 } 882 wg.Wait() 883 884 blobs.Lock() 885 886 c.Assert(blobs.m, gc.HasLen, 10) 887 888 // Verify there is only a single uploaded charm remains and it 889 // contains the correct data. 890 sch, err := s.State.Charm(curl) 891 c.Assert(err, jc.ErrorIsNil) 892 storagePath := sch.StoragePath() 893 c.Assert(blobs.m[storagePath], jc.IsTrue) 894 for path, exists := range blobs.m { 895 if path != storagePath { 896 c.Assert(exists, jc.IsFalse) 897 } 898 } 899 900 storage := statestorage.NewStorage(s.State.ModelUUID(), s.State.MongoSession()) 901 s.assertUploaded(c, storage, sch.StoragePath(), sch.BundleSha256()) 902 } 903 904 func (s *applicationSuite) assertUploaded(c *gc.C, storage statestorage.Storage, storagePath, expectedSHA256 string) { 905 reader, _, err := storage.Get(storagePath) 906 c.Assert(err, jc.ErrorIsNil) 907 defer reader.Close() 908 downloadedSHA256, _, err := utils.ReadSHA256(reader) 909 c.Assert(err, jc.ErrorIsNil) 910 c.Assert(downloadedSHA256, gc.Equals, expectedSHA256) 911 } 912 913 func (s *applicationSuite) TestAddCharmOverwritesPlaceholders(c *gc.C) { 914 client := s.APIState.Client() 915 curl, _ := s.UploadCharm(c, "trusty/wordpress-42", "wordpress") 916 917 // Add a placeholder with the same charm URL. 918 err := s.State.AddStoreCharmPlaceholder(curl) 919 c.Assert(err, jc.ErrorIsNil) 920 _, err = s.State.Charm(curl) 921 c.Assert(err, jc.Satisfies, errors.IsNotFound) 922 923 // Now try to add the charm, which will convert the placeholder to 924 // a pending charm. 925 err = client.AddCharm(curl, csparams.StableChannel, false) 926 c.Assert(err, jc.ErrorIsNil) 927 928 // Make sure the document's flags were reset as expected. 929 sch, err := s.State.Charm(curl) 930 c.Assert(err, jc.ErrorIsNil) 931 c.Assert(sch.URL(), jc.DeepEquals, curl) 932 c.Assert(sch.IsPlaceholder(), jc.IsFalse) 933 c.Assert(sch.IsUploaded(), jc.IsTrue) 934 } 935 936 func (s *applicationSuite) TestApplicationGetCharmURL(c *gc.C) { 937 s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 938 result, err := s.applicationAPI.GetCharmURL(params.ApplicationGet{ApplicationName: "wordpress"}) 939 c.Assert(err, jc.ErrorIsNil) 940 c.Assert(result.Error, gc.IsNil) 941 c.Assert(result.Result, gc.Equals, "local:quantal/wordpress-3") 942 } 943 944 func (s *applicationSuite) TestApplicationSetCharm(c *gc.C) { 945 curl, _ := s.UploadCharm(c, "precise/dummy-0", "dummy") 946 err := application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{ 947 URL: curl.String(), 948 }) 949 c.Assert(err, jc.ErrorIsNil) 950 results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{ 951 Applications: []params.ApplicationDeploy{{ 952 CharmURL: curl.String(), 953 ApplicationName: "application", 954 NumUnits: 3, 955 }}}) 956 c.Assert(err, jc.ErrorIsNil) 957 c.Assert(results.Results, gc.HasLen, 1) 958 c.Assert(results.Results[0].Error, gc.IsNil) 959 curl, _ = s.UploadCharm(c, "precise/wordpress-3", "wordpress") 960 err = application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{ 961 URL: curl.String(), 962 }) 963 c.Assert(err, jc.ErrorIsNil) 964 err = s.applicationAPI.SetCharm(params.ApplicationSetCharm{ 965 ApplicationName: "application", 966 CharmURL: curl.String(), 967 }) 968 c.Assert(err, jc.ErrorIsNil) 969 970 // Ensure that the charm is not marked as forced. 971 app, err := s.State.Application("application") 972 c.Assert(err, jc.ErrorIsNil) 973 charm, force, err := app.Charm() 974 c.Assert(err, jc.ErrorIsNil) 975 c.Assert(charm.URL().String(), gc.Equals, curl.String()) 976 c.Assert(force, jc.IsFalse) 977 } 978 979 func (s *applicationSuite) setupApplicationSetCharm(c *gc.C) { 980 curl, _ := s.UploadCharm(c, "precise/dummy-0", "dummy") 981 err := application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{ 982 URL: curl.String(), 983 }) 984 c.Assert(err, jc.ErrorIsNil) 985 results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{ 986 Applications: []params.ApplicationDeploy{{ 987 CharmURL: curl.String(), 988 ApplicationName: "application", 989 NumUnits: 3, 990 }}}) 991 c.Assert(err, jc.ErrorIsNil) 992 c.Assert(results.Results, gc.HasLen, 1) 993 c.Assert(results.Results[0].Error, gc.IsNil) 994 curl, _ = s.UploadCharm(c, "precise/wordpress-3", "wordpress") 995 err = application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{ 996 URL: curl.String(), 997 }) 998 c.Assert(err, jc.ErrorIsNil) 999 } 1000 1001 func (s *applicationSuite) assertApplicationSetCharm(c *gc.C, forceUnits bool) { 1002 err := s.applicationAPI.SetCharm(params.ApplicationSetCharm{ 1003 ApplicationName: "application", 1004 CharmURL: "cs:~who/precise/wordpress-3", 1005 ForceUnits: forceUnits, 1006 }) 1007 c.Assert(err, jc.ErrorIsNil) 1008 // Ensure that the charm is not marked as forced. 1009 app, err := s.State.Application("application") 1010 c.Assert(err, jc.ErrorIsNil) 1011 charm, _, err := app.Charm() 1012 c.Assert(err, jc.ErrorIsNil) 1013 c.Assert(charm.URL().String(), gc.Equals, "cs:~who/precise/wordpress-3") 1014 } 1015 1016 func (s *applicationSuite) assertApplicationSetCharmBlocked(c *gc.C, msg string) { 1017 err := s.applicationAPI.SetCharm(params.ApplicationSetCharm{ 1018 ApplicationName: "application", 1019 CharmURL: "cs:~who/precise/wordpress-3", 1020 }) 1021 s.AssertBlocked(c, err, msg) 1022 } 1023 1024 func (s *applicationSuite) TestBlockDestroyApplicationSetCharm(c *gc.C) { 1025 s.setupApplicationSetCharm(c) 1026 s.BlockDestroyModel(c, "TestBlockDestroyApplicationSetCharm") 1027 s.assertApplicationSetCharm(c, false) 1028 } 1029 1030 func (s *applicationSuite) TestBlockRemoveApplicationSetCharm(c *gc.C) { 1031 s.setupApplicationSetCharm(c) 1032 s.BlockRemoveObject(c, "TestBlockRemoveApplicationSetCharm") 1033 s.assertApplicationSetCharm(c, false) 1034 } 1035 1036 func (s *applicationSuite) TestBlockChangesApplicationSetCharm(c *gc.C) { 1037 s.setupApplicationSetCharm(c) 1038 s.BlockAllChanges(c, "TestBlockChangesApplicationSetCharm") 1039 s.assertApplicationSetCharmBlocked(c, "TestBlockChangesApplicationSetCharm") 1040 } 1041 1042 func (s *applicationSuite) TestApplicationSetCharmForceUnits(c *gc.C) { 1043 curl, _ := s.UploadCharm(c, "precise/dummy-0", "dummy") 1044 err := application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{ 1045 URL: curl.String(), 1046 }) 1047 c.Assert(err, jc.ErrorIsNil) 1048 results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{ 1049 Applications: []params.ApplicationDeploy{{ 1050 CharmURL: curl.String(), 1051 ApplicationName: "application", 1052 NumUnits: 3, 1053 }}}) 1054 c.Assert(err, jc.ErrorIsNil) 1055 c.Assert(results.Results, gc.HasLen, 1) 1056 c.Assert(results.Results[0].Error, gc.IsNil) 1057 curl, _ = s.UploadCharm(c, "precise/wordpress-3", "wordpress") 1058 err = application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{ 1059 URL: curl.String(), 1060 }) 1061 c.Assert(err, jc.ErrorIsNil) 1062 err = s.applicationAPI.SetCharm(params.ApplicationSetCharm{ 1063 ApplicationName: "application", 1064 CharmURL: curl.String(), 1065 ForceUnits: true, 1066 }) 1067 c.Assert(err, jc.ErrorIsNil) 1068 1069 // Ensure that the charm is marked as forced. 1070 app, err := s.State.Application("application") 1071 c.Assert(err, jc.ErrorIsNil) 1072 charm, force, err := app.Charm() 1073 c.Assert(err, jc.ErrorIsNil) 1074 c.Assert(charm.URL().String(), gc.Equals, curl.String()) 1075 c.Assert(force, jc.IsTrue) 1076 } 1077 1078 func (s *applicationSuite) TestBlockApplicationSetCharmForce(c *gc.C) { 1079 s.setupApplicationSetCharm(c) 1080 1081 // block all changes 1082 s.BlockAllChanges(c, "TestBlockApplicationSetCharmForce") 1083 s.BlockRemoveObject(c, "TestBlockApplicationSetCharmForce") 1084 s.BlockDestroyModel(c, "TestBlockApplicationSetCharmForce") 1085 1086 s.assertApplicationSetCharm(c, true) 1087 } 1088 1089 func (s *applicationSuite) TestApplicationSetCharmInvalidApplication(c *gc.C) { 1090 err := s.applicationAPI.SetCharm(params.ApplicationSetCharm{ 1091 ApplicationName: "badapplication", 1092 CharmURL: "cs:precise/wordpress-3", 1093 ForceSeries: true, 1094 ForceUnits: true, 1095 }) 1096 c.Assert(err, gc.ErrorMatches, `application "badapplication" not found`) 1097 } 1098 1099 func (s *applicationSuite) TestApplicationAddCharmErrors(c *gc.C) { 1100 for url, expect := range map[string]string{ 1101 "wordpress": "charm URL must include revision", 1102 "cs:wordpress": "charm URL must include revision", 1103 "cs:precise/wordpress": "charm URL must include revision", 1104 "cs:precise/wordpress-999999": `cannot retrieve "cs:precise/wordpress-999999": charm not found`, 1105 } { 1106 c.Logf("test %s", url) 1107 err := application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{ 1108 URL: url, 1109 }) 1110 c.Check(err, gc.ErrorMatches, expect) 1111 } 1112 } 1113 1114 func (s *applicationSuite) TestApplicationSetCharmLegacy(c *gc.C) { 1115 curl, _ := s.UploadCharm(c, "precise/dummy-0", "dummy") 1116 err := application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{ 1117 URL: curl.String(), 1118 }) 1119 c.Assert(err, jc.ErrorIsNil) 1120 results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{ 1121 Applications: []params.ApplicationDeploy{{ 1122 CharmURL: curl.String(), 1123 ApplicationName: "application", 1124 }}}) 1125 c.Assert(err, jc.ErrorIsNil) 1126 c.Assert(results.Results, gc.HasLen, 1) 1127 c.Assert(results.Results[0].Error, gc.IsNil) 1128 curl, _ = s.UploadCharm(c, "trusty/dummy-1", "dummy") 1129 err = application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{ 1130 URL: curl.String(), 1131 }) 1132 c.Assert(err, jc.ErrorIsNil) 1133 1134 // Even with forceSeries = true, we can't change a charm where 1135 // the series is specified in the URL. 1136 err = s.applicationAPI.SetCharm(params.ApplicationSetCharm{ 1137 ApplicationName: "application", 1138 CharmURL: curl.String(), 1139 ForceSeries: true, 1140 }) 1141 c.Assert(err, gc.ErrorMatches, `cannot upgrade application "application" to charm "cs:~who/trusty/dummy-1": cannot change an application's series`) 1142 } 1143 1144 func (s *applicationSuite) TestApplicationSetCharmUnsupportedSeries(c *gc.C) { 1145 curl, _ := s.UploadCharmMultiSeries(c, "~who/multi-series", "multi-series") 1146 err := application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{ 1147 URL: curl.String(), 1148 }) 1149 c.Assert(err, jc.ErrorIsNil) 1150 results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{ 1151 Applications: []params.ApplicationDeploy{{ 1152 CharmURL: curl.String(), 1153 ApplicationName: "application", 1154 Series: "precise", 1155 }}}) 1156 c.Assert(err, jc.ErrorIsNil) 1157 c.Assert(results.Results, gc.HasLen, 1) 1158 c.Assert(results.Results[0].Error, gc.IsNil) 1159 curl, _ = s.UploadCharmMultiSeries(c, "~who/multi-series", "multi-series2") 1160 err = application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{ 1161 URL: curl.String(), 1162 }) 1163 c.Assert(err, jc.ErrorIsNil) 1164 1165 err = s.applicationAPI.SetCharm(params.ApplicationSetCharm{ 1166 ApplicationName: "application", 1167 CharmURL: curl.String(), 1168 }) 1169 c.Assert(err, gc.ErrorMatches, `cannot upgrade application "application" to charm "cs:~who/multi-series-1": only these series are supported: trusty, wily`) 1170 } 1171 1172 func (s *applicationSuite) assertApplicationSetCharmSeries(c *gc.C, upgradeCharm, series string) { 1173 curl, _ := s.UploadCharmMultiSeries(c, "~who/multi-series", "multi-series") 1174 err := application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{ 1175 URL: curl.String(), 1176 }) 1177 c.Assert(err, jc.ErrorIsNil) 1178 results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{ 1179 Applications: []params.ApplicationDeploy{{ 1180 CharmURL: curl.String(), 1181 ApplicationName: "application", 1182 Series: "precise", 1183 }}}) 1184 c.Assert(err, jc.ErrorIsNil) 1185 c.Assert(results.Results, gc.HasLen, 1) 1186 c.Assert(results.Results[0].Error, gc.IsNil) 1187 1188 url := upgradeCharm 1189 if series != "" { 1190 url = series + "/" + upgradeCharm 1191 } 1192 curl, _ = s.UploadCharmMultiSeries(c, "~who/"+url, upgradeCharm) 1193 err = application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{ 1194 URL: curl.String(), 1195 }) 1196 c.Assert(err, jc.ErrorIsNil) 1197 1198 err = s.applicationAPI.SetCharm(params.ApplicationSetCharm{ 1199 ApplicationName: "application", 1200 CharmURL: curl.String(), 1201 ForceSeries: true, 1202 }) 1203 c.Assert(err, jc.ErrorIsNil) 1204 app, err := s.State.Application("application") 1205 c.Assert(err, jc.ErrorIsNil) 1206 ch, _, err := app.Charm() 1207 c.Assert(err, jc.ErrorIsNil) 1208 c.Assert(ch.URL().String(), gc.Equals, "cs:~who/"+url+"-0") 1209 } 1210 1211 func (s *applicationSuite) TestApplicationSetCharmUnsupportedSeriesForce(c *gc.C) { 1212 s.assertApplicationSetCharmSeries(c, "multi-series2", "") 1213 } 1214 1215 func (s *applicationSuite) TestApplicationSetCharmNoExplicitSupportedSeries(c *gc.C) { 1216 s.assertApplicationSetCharmSeries(c, "dummy", "precise") 1217 } 1218 1219 func (s *applicationSuite) TestApplicationSetCharmWrongOS(c *gc.C) { 1220 curl, _ := s.UploadCharmMultiSeries(c, "~who/multi-series", "multi-series") 1221 err := application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{ 1222 URL: curl.String(), 1223 }) 1224 c.Assert(err, jc.ErrorIsNil) 1225 results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{ 1226 Applications: []params.ApplicationDeploy{{ 1227 CharmURL: curl.String(), 1228 ApplicationName: "application", 1229 Series: "precise", 1230 }}}) 1231 c.Assert(err, jc.ErrorIsNil) 1232 c.Assert(results.Results, gc.HasLen, 1) 1233 c.Assert(results.Results[0].Error, gc.IsNil) 1234 curl, _ = s.UploadCharmMultiSeries(c, "~who/multi-series-windows", "multi-series-windows") 1235 err = application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{ 1236 URL: curl.String(), 1237 }) 1238 c.Assert(err, jc.ErrorIsNil) 1239 1240 err = s.applicationAPI.SetCharm(params.ApplicationSetCharm{ 1241 ApplicationName: "application", 1242 CharmURL: curl.String(), 1243 ForceSeries: true, 1244 }) 1245 c.Assert(err, gc.ErrorMatches, `cannot upgrade application "application" to charm "cs:~who/multi-series-windows-0": OS "Ubuntu" not supported by charm`) 1246 } 1247 1248 type testModeCharmRepo struct { 1249 *charmrepo.CharmStore 1250 testMode bool 1251 } 1252 1253 // WithTestMode returns a repository Interface where test mode is enabled. 1254 func (s *testModeCharmRepo) WithTestMode() charmrepo.Interface { 1255 s.testMode = true 1256 return s.CharmStore.WithTestMode() 1257 } 1258 1259 func (s *applicationSuite) TestSpecializeStoreOnDeployApplicationSetCharmAndAddCharm(c *gc.C) { 1260 repo := &testModeCharmRepo{} 1261 s.PatchValue(&csclient.ServerURL, s.Srv.URL) 1262 newCharmStoreRepo := application.NewCharmStoreRepo 1263 s.PatchValue(&application.NewCharmStoreRepo, func(c *csclient.Client) charmrepo.Interface { 1264 repo.CharmStore = newCharmStoreRepo(c).(*charmrepo.CharmStore) 1265 return repo 1266 }) 1267 attrs := map[string]interface{}{"test-mode": true} 1268 err := s.Model.UpdateModelConfig(attrs, nil) 1269 c.Assert(err, jc.ErrorIsNil) 1270 1271 // Check that the store's test mode is enabled when calling application Deploy. 1272 curl, _ := s.UploadCharm(c, "trusty/dummy-1", "dummy") 1273 err = application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{ 1274 URL: curl.String(), 1275 }) 1276 c.Assert(err, jc.ErrorIsNil) 1277 results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{ 1278 Applications: []params.ApplicationDeploy{{ 1279 CharmURL: curl.String(), 1280 ApplicationName: "application", 1281 NumUnits: 3, 1282 }}}) 1283 c.Assert(err, jc.ErrorIsNil) 1284 c.Assert(results.Results, gc.HasLen, 1) 1285 c.Assert(results.Results[0].Error, gc.IsNil) 1286 c.Assert(repo.testMode, jc.IsTrue) 1287 1288 // Check that the store's test mode is enabled when calling SetCharm. 1289 curl, _ = s.UploadCharm(c, "trusty/wordpress-2", "wordpress") 1290 err = s.applicationAPI.SetCharm(params.ApplicationSetCharm{ 1291 ApplicationName: "application", 1292 CharmURL: curl.String(), 1293 }) 1294 c.Assert(repo.testMode, jc.IsTrue) 1295 1296 // Check that the store's test mode is enabled when calling AddCharm. 1297 curl, _ = s.UploadCharm(c, "utopic/riak-42", "riak") 1298 err = s.APIState.Client().AddCharm(curl, csparams.StableChannel, false) 1299 c.Assert(err, jc.ErrorIsNil) 1300 c.Assert(repo.testMode, jc.IsTrue) 1301 } 1302 1303 func (s *applicationSuite) setupApplicationDeploy(c *gc.C, args string) (*charm.URL, charm.Charm, constraints.Value) { 1304 curl, ch := s.UploadCharm(c, "precise/dummy-42", "dummy") 1305 err := application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{ 1306 URL: curl.String(), 1307 }) 1308 c.Assert(err, jc.ErrorIsNil) 1309 cons := constraints.MustParse(args) 1310 return curl, ch, cons 1311 } 1312 1313 func (s *applicationSuite) assertApplicationDeployPrincipal(c *gc.C, curl *charm.URL, ch charm.Charm, mem4g constraints.Value) { 1314 results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{ 1315 Applications: []params.ApplicationDeploy{{ 1316 CharmURL: curl.String(), 1317 ApplicationName: "application", 1318 NumUnits: 3, 1319 Constraints: mem4g, 1320 }}}) 1321 c.Assert(err, jc.ErrorIsNil) 1322 c.Assert(results.Results, gc.HasLen, 1) 1323 c.Assert(results.Results[0].Error, gc.IsNil) 1324 apiservertesting.AssertPrincipalApplicationDeployed(c, s.State, "application", curl, false, ch, mem4g) 1325 } 1326 1327 func (s *applicationSuite) assertApplicationDeployPrincipalBlocked(c *gc.C, msg string, curl *charm.URL, mem4g constraints.Value) { 1328 _, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{ 1329 Applications: []params.ApplicationDeploy{{ 1330 CharmURL: curl.String(), 1331 ApplicationName: "application", 1332 NumUnits: 3, 1333 Constraints: mem4g, 1334 }}}) 1335 s.AssertBlocked(c, err, msg) 1336 } 1337 1338 func (s *applicationSuite) TestBlockDestroyApplicationDeployPrincipal(c *gc.C) { 1339 curl, bundle, cons := s.setupApplicationDeploy(c, "mem=4G") 1340 s.BlockDestroyModel(c, "TestBlockDestroyApplicationDeployPrincipal") 1341 s.assertApplicationDeployPrincipal(c, curl, bundle, cons) 1342 } 1343 1344 func (s *applicationSuite) TestBlockRemoveApplicationDeployPrincipal(c *gc.C) { 1345 curl, bundle, cons := s.setupApplicationDeploy(c, "mem=4G") 1346 s.BlockRemoveObject(c, "TestBlockRemoveApplicationDeployPrincipal") 1347 s.assertApplicationDeployPrincipal(c, curl, bundle, cons) 1348 } 1349 1350 func (s *applicationSuite) TestBlockChangesApplicationDeployPrincipal(c *gc.C) { 1351 curl, _, cons := s.setupApplicationDeploy(c, "mem=4G") 1352 s.BlockAllChanges(c, "TestBlockChangesApplicationDeployPrincipal") 1353 s.assertApplicationDeployPrincipalBlocked(c, "TestBlockChangesApplicationDeployPrincipal", curl, cons) 1354 } 1355 1356 func (s *applicationSuite) TestApplicationDeploySubordinate(c *gc.C) { 1357 curl, ch := s.UploadCharm(c, "utopic/logging-47", "logging") 1358 err := application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{ 1359 URL: curl.String(), 1360 }) 1361 c.Assert(err, jc.ErrorIsNil) 1362 results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{ 1363 Applications: []params.ApplicationDeploy{{ 1364 CharmURL: curl.String(), 1365 ApplicationName: "application-name", 1366 }}}) 1367 c.Assert(err, jc.ErrorIsNil) 1368 c.Assert(results.Results, gc.HasLen, 1) 1369 c.Assert(results.Results[0].Error, gc.IsNil) 1370 1371 app, err := s.State.Application("application-name") 1372 c.Assert(err, jc.ErrorIsNil) 1373 charm, force, err := app.Charm() 1374 c.Assert(err, jc.ErrorIsNil) 1375 c.Assert(force, jc.IsFalse) 1376 c.Assert(charm.URL(), gc.DeepEquals, curl) 1377 c.Assert(charm.Meta(), gc.DeepEquals, ch.Meta()) 1378 c.Assert(charm.Config(), gc.DeepEquals, ch.Config()) 1379 1380 units, err := app.AllUnits() 1381 c.Assert(err, jc.ErrorIsNil) 1382 c.Assert(units, gc.HasLen, 0) 1383 } 1384 1385 func (s *applicationSuite) combinedSettings(ch *state.Charm, inSettings charm.Settings) charm.Settings { 1386 result := ch.Config().DefaultSettings() 1387 for name, value := range inSettings { 1388 result[name] = value 1389 } 1390 return result 1391 } 1392 1393 func (s *applicationSuite) TestApplicationDeployConfig(c *gc.C) { 1394 curl, _ := s.UploadCharm(c, "precise/dummy-0", "dummy") 1395 err := application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{ 1396 URL: curl.String(), 1397 }) 1398 c.Assert(err, jc.ErrorIsNil) 1399 results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{ 1400 Applications: []params.ApplicationDeploy{{ 1401 CharmURL: curl.String(), 1402 ApplicationName: "application-name", 1403 NumUnits: 1, 1404 ConfigYAML: "application-name:\n username: fred", 1405 }}}) 1406 c.Assert(err, jc.ErrorIsNil) 1407 c.Assert(results.Results, gc.HasLen, 1) 1408 c.Assert(results.Results[0].Error, gc.IsNil) 1409 1410 app, err := s.State.Application("application-name") 1411 c.Assert(err, jc.ErrorIsNil) 1412 settings, err := app.CharmConfig() 1413 c.Assert(err, jc.ErrorIsNil) 1414 ch, _, err := app.Charm() 1415 c.Assert(err, jc.ErrorIsNil) 1416 c.Assert(settings, gc.DeepEquals, s.combinedSettings(ch, charm.Settings{"username": "fred"})) 1417 } 1418 1419 func (s *applicationSuite) TestApplicationDeployConfigError(c *gc.C) { 1420 // TODO(fwereade): test Config/ConfigYAML handling directly on srvClient. 1421 // Can't be done cleanly until it's extracted similarly to Machiner. 1422 curl, _ := s.UploadCharm(c, "precise/dummy-0", "dummy") 1423 err := application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{ 1424 URL: curl.String(), 1425 }) 1426 c.Assert(err, jc.ErrorIsNil) 1427 results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{ 1428 Applications: []params.ApplicationDeploy{{ 1429 CharmURL: curl.String(), 1430 ApplicationName: "application-name", 1431 NumUnits: 1, 1432 ConfigYAML: "application-name:\n skill-level: fred", 1433 }}}) 1434 c.Assert(err, jc.ErrorIsNil) 1435 c.Assert(results.Results, gc.HasLen, 1) 1436 c.Assert(results.Results[0].Error, gc.ErrorMatches, `option "skill-level" expected int, got "fred"`) 1437 _, err = s.State.Application("application-name") 1438 c.Assert(err, jc.Satisfies, errors.IsNotFound) 1439 } 1440 1441 func (s *applicationSuite) TestApplicationDeployToMachine(c *gc.C) { 1442 curl, ch := s.UploadCharm(c, "precise/dummy-0", "dummy") 1443 err := application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{ 1444 URL: curl.String(), 1445 }) 1446 c.Assert(err, jc.ErrorIsNil) 1447 1448 machine, err := s.State.AddMachine("precise", state.JobHostUnits) 1449 c.Assert(err, jc.ErrorIsNil) 1450 results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{ 1451 Applications: []params.ApplicationDeploy{{ 1452 CharmURL: curl.String(), 1453 ApplicationName: "application-name", 1454 NumUnits: 1, 1455 ConfigYAML: "application-name:\n username: fred", 1456 }}}) 1457 c.Assert(err, jc.ErrorIsNil) 1458 c.Assert(results.Results, gc.HasLen, 1) 1459 c.Assert(results.Results[0].Error, gc.IsNil) 1460 1461 app, err := s.State.Application("application-name") 1462 c.Assert(err, jc.ErrorIsNil) 1463 charm, force, err := app.Charm() 1464 c.Assert(err, jc.ErrorIsNil) 1465 c.Assert(force, jc.IsFalse) 1466 c.Assert(charm.URL(), gc.DeepEquals, curl) 1467 c.Assert(charm.Meta(), gc.DeepEquals, ch.Meta()) 1468 c.Assert(charm.Config(), gc.DeepEquals, ch.Config()) 1469 1470 errs, err := s.APIState.UnitAssigner().AssignUnits([]names.UnitTag{names.NewUnitTag("application-name/0")}) 1471 c.Assert(errs, gc.DeepEquals, []error{nil}) 1472 c.Assert(err, jc.ErrorIsNil) 1473 1474 units, err := app.AllUnits() 1475 c.Assert(err, jc.ErrorIsNil) 1476 c.Assert(units, gc.HasLen, 1) 1477 1478 mid, err := units[0].AssignedMachineId() 1479 c.Assert(err, jc.ErrorIsNil) 1480 c.Assert(mid, gc.Equals, machine.Id()) 1481 } 1482 1483 func (s *applicationSuite) TestApplicationDeployToMachineWithLXDProfile(c *gc.C) { 1484 curl, ch := s.UploadCharm(c, "quantal/lxd-profile-0", "lxd-profile") 1485 err := application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{ 1486 URL: curl.String(), 1487 }) 1488 c.Assert(err, jc.ErrorIsNil) 1489 1490 machine, err := s.State.AddMachine("quantal", state.JobHostUnits) 1491 c.Assert(err, jc.ErrorIsNil) 1492 results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{ 1493 Applications: []params.ApplicationDeploy{{ 1494 CharmURL: curl.String(), 1495 ApplicationName: "application-name", 1496 NumUnits: 1, 1497 }}}) 1498 c.Assert(err, jc.ErrorIsNil) 1499 c.Assert(results.Results, gc.HasLen, 1) 1500 c.Assert(results.Results[0].Error, gc.IsNil) 1501 1502 application, err := s.State.Application("application-name") 1503 c.Assert(err, jc.ErrorIsNil) 1504 expected, force, err := application.Charm() 1505 c.Assert(err, jc.ErrorIsNil) 1506 c.Assert(force, jc.IsFalse) 1507 c.Assert(expected.URL(), gc.DeepEquals, curl) 1508 c.Assert(expected.Meta(), gc.DeepEquals, ch.Meta()) 1509 c.Assert(expected.Config(), gc.DeepEquals, ch.Config()) 1510 c.Assert(expected.LXDProfile(), gc.DeepEquals, ch.(charm.LXDProfiler).LXDProfile()) 1511 1512 errs, err := s.APIState.UnitAssigner().AssignUnits([]names.UnitTag{names.NewUnitTag("application-name/0")}) 1513 c.Assert(errs, gc.DeepEquals, []error{nil}) 1514 c.Assert(err, jc.ErrorIsNil) 1515 1516 units, err := application.AllUnits() 1517 c.Assert(err, jc.ErrorIsNil) 1518 c.Assert(units, gc.HasLen, 1) 1519 1520 mid, err := units[0].AssignedMachineId() 1521 c.Assert(err, jc.ErrorIsNil) 1522 c.Assert(mid, gc.Equals, machine.Id()) 1523 } 1524 1525 func (s *applicationSuite) TestApplicationDeployToMachineWithInvalidLXDProfile(c *gc.C) { 1526 curl, _ := s.UploadCharm(c, "quantal/lxd-profile-fail-0", "lxd-profile-fail") 1527 err := application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{ 1528 URL: curl.String(), 1529 }) 1530 c.Assert(err, gc.ErrorMatches, `.*invalid lxd-profile.yaml: contains device type "unix-disk"`) 1531 } 1532 1533 func (s *applicationSuite) TestApplicationDeployToMachineWithInvalidLXDProfileAndForceStillSucceeds(c *gc.C) { 1534 curl, ch := s.UploadCharm(c, "quantal/lxd-profile-fail-0", "lxd-profile-fail") 1535 err := application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{ 1536 URL: curl.String(), 1537 Force: true, 1538 }) 1539 c.Assert(err, jc.ErrorIsNil) 1540 1541 machine, err := s.State.AddMachine("quantal", state.JobHostUnits) 1542 c.Assert(err, jc.ErrorIsNil) 1543 results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{ 1544 Applications: []params.ApplicationDeploy{{ 1545 CharmURL: curl.String(), 1546 ApplicationName: "application-name", 1547 NumUnits: 1, 1548 }}}) 1549 c.Assert(err, jc.ErrorIsNil) 1550 c.Assert(results.Results, gc.HasLen, 1) 1551 c.Assert(results.Results[0].Error, gc.IsNil) 1552 1553 app, err := s.State.Application("application-name") 1554 c.Assert(err, jc.ErrorIsNil) 1555 expected, force, err := app.Charm() 1556 c.Assert(err, jc.ErrorIsNil) 1557 c.Assert(force, jc.IsFalse) 1558 c.Assert(expected.URL(), gc.DeepEquals, curl) 1559 c.Assert(expected.Meta(), gc.DeepEquals, ch.Meta()) 1560 c.Assert(expected.Config(), gc.DeepEquals, ch.Config()) 1561 c.Assert(expected.LXDProfile(), gc.DeepEquals, ch.(charm.LXDProfiler).LXDProfile()) 1562 1563 errs, err := s.APIState.UnitAssigner().AssignUnits([]names.UnitTag{names.NewUnitTag("application-name/0")}) 1564 c.Assert(errs, gc.DeepEquals, []error{nil}) 1565 c.Assert(err, jc.ErrorIsNil) 1566 1567 units, err := app.AllUnits() 1568 c.Assert(err, jc.ErrorIsNil) 1569 c.Assert(units, gc.HasLen, 1) 1570 1571 mid, err := units[0].AssignedMachineId() 1572 c.Assert(err, jc.ErrorIsNil) 1573 c.Assert(mid, gc.Equals, machine.Id()) 1574 } 1575 1576 func (s *applicationSuite) TestApplicationDeployToMachineNotFound(c *gc.C) { 1577 results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{ 1578 Applications: []params.ApplicationDeploy{{ 1579 CharmURL: "cs:precise/application-name-1", 1580 ApplicationName: "application-name", 1581 NumUnits: 1, 1582 Placement: []*instance.Placement{instance.MustParsePlacement("42")}, 1583 }}}) 1584 c.Assert(err, jc.ErrorIsNil) 1585 c.Assert(results.Results, gc.HasLen, 1) 1586 c.Assert(results.Results[0].Error, gc.ErrorMatches, `cannot deploy "application-name" to machine 42: machine 42 not found`) 1587 1588 _, err = s.State.Application("application-name") 1589 c.Assert(err, gc.ErrorMatches, `application "application-name" not found`) 1590 } 1591 1592 func (s *applicationSuite) deployApplicationForUpdateTests(c *gc.C) { 1593 curl, _ := s.UploadCharm(c, "precise/dummy-1", "dummy") 1594 err := application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{ 1595 URL: curl.String(), 1596 }) 1597 c.Assert(err, jc.ErrorIsNil) 1598 results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{ 1599 Applications: []params.ApplicationDeploy{{ 1600 CharmURL: curl.String(), 1601 ApplicationName: "application", 1602 NumUnits: 1, 1603 }}}) 1604 c.Assert(err, jc.ErrorIsNil) 1605 c.Assert(results.Results, gc.HasLen, 1) 1606 c.Assert(results.Results[0].Error, gc.IsNil) 1607 } 1608 1609 func (s *applicationSuite) checkClientApplicationUpdateSetCharm(c *gc.C, forceCharmURL bool) { 1610 s.deployApplicationForUpdateTests(c) 1611 curl, _ := s.UploadCharm(c, "precise/wordpress-3", "wordpress") 1612 err := application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{ 1613 URL: curl.String(), 1614 }) 1615 c.Assert(err, jc.ErrorIsNil) 1616 1617 // Update the charm for the application. 1618 args := params.ApplicationUpdate{ 1619 ApplicationName: "application", 1620 CharmURL: curl.String(), 1621 ForceCharmURL: forceCharmURL, 1622 } 1623 err = s.applicationAPI.Update(args) 1624 c.Assert(err, jc.ErrorIsNil) 1625 1626 // Ensure the charm has been updated and and the force flag correctly set. 1627 app, err := s.State.Application("application") 1628 c.Assert(err, jc.ErrorIsNil) 1629 ch, force, err := app.Charm() 1630 c.Assert(err, jc.ErrorIsNil) 1631 c.Assert(ch.URL().String(), gc.Equals, curl.String()) 1632 c.Assert(force, gc.Equals, forceCharmURL) 1633 } 1634 1635 func (s *applicationSuite) TestApplicationUpdateSetCharm(c *gc.C) { 1636 s.checkClientApplicationUpdateSetCharm(c, false) 1637 } 1638 1639 func (s *applicationSuite) TestBlockDestroyApplicationUpdate(c *gc.C) { 1640 s.BlockDestroyModel(c, "TestBlockDestroyApplicationUpdate") 1641 s.checkClientApplicationUpdateSetCharm(c, false) 1642 } 1643 1644 func (s *applicationSuite) TestBlockRemoveApplicationUpdate(c *gc.C) { 1645 s.BlockRemoveObject(c, "TestBlockRemoveApplicationUpdate") 1646 s.checkClientApplicationUpdateSetCharm(c, false) 1647 } 1648 1649 func (s *applicationSuite) setupApplicationUpdate(c *gc.C) string { 1650 s.deployApplicationForUpdateTests(c) 1651 curl, _ := s.UploadCharm(c, "precise/wordpress-3", "wordpress") 1652 err := application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{ 1653 URL: curl.String(), 1654 }) 1655 c.Assert(err, jc.ErrorIsNil) 1656 return curl.String() 1657 } 1658 1659 func (s *applicationSuite) TestBlockChangeApplicationUpdate(c *gc.C) { 1660 curl := s.setupApplicationUpdate(c) 1661 s.BlockAllChanges(c, "TestBlockChangeApplicationUpdate") 1662 // Update the charm for the application. 1663 args := params.ApplicationUpdate{ 1664 ApplicationName: "application", 1665 CharmURL: curl, 1666 ForceCharmURL: false, 1667 } 1668 err := s.applicationAPI.Update(args) 1669 s.AssertBlocked(c, err, "TestBlockChangeApplicationUpdate") 1670 } 1671 1672 func (s *applicationSuite) TestApplicationUpdateForceSetCharm(c *gc.C) { 1673 s.checkClientApplicationUpdateSetCharm(c, true) 1674 } 1675 1676 func (s *applicationSuite) TestBlockApplicationUpdateForced(c *gc.C) { 1677 curl := s.setupApplicationUpdate(c) 1678 1679 // block all changes. Force should ignore block :) 1680 s.BlockAllChanges(c, "TestBlockApplicationUpdateForced") 1681 s.BlockDestroyModel(c, "TestBlockApplicationUpdateForced") 1682 s.BlockRemoveObject(c, "TestBlockApplicationUpdateForced") 1683 1684 // Update the charm for the application. 1685 args := params.ApplicationUpdate{ 1686 ApplicationName: "application", 1687 CharmURL: curl, 1688 ForceCharmURL: true, 1689 } 1690 err := s.applicationAPI.Update(args) 1691 c.Assert(err, jc.ErrorIsNil) 1692 1693 // Ensure the charm has been updated and and the force flag correctly set. 1694 app, err := s.State.Application("application") 1695 c.Assert(err, jc.ErrorIsNil) 1696 ch, force, err := app.Charm() 1697 c.Assert(err, jc.ErrorIsNil) 1698 c.Assert(ch.URL().String(), gc.Equals, curl) 1699 c.Assert(force, jc.IsTrue) 1700 } 1701 1702 func (s *applicationSuite) TestApplicationUpdateSetCharmNotFound(c *gc.C) { 1703 s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 1704 args := params.ApplicationUpdate{ 1705 ApplicationName: "wordpress", 1706 CharmURL: "cs:precise/wordpress-999999", 1707 } 1708 err := s.applicationAPI.Update(args) 1709 c.Check(err, gc.ErrorMatches, `charm "cs:precise/wordpress-999999" not found`) 1710 } 1711 1712 func (s *applicationSuite) TestApplicationUpdateSetMinUnits(c *gc.C) { 1713 app := s.AddTestingApplication(c, "dummy", s.AddTestingCharm(c, "dummy")) 1714 1715 // Set minimum units for the application. 1716 minUnits := 2 1717 args := params.ApplicationUpdate{ 1718 ApplicationName: "dummy", 1719 MinUnits: &minUnits, 1720 } 1721 err := s.applicationAPI.Update(args) 1722 c.Assert(err, jc.ErrorIsNil) 1723 1724 // Ensure the minimum number of units has been set. 1725 c.Assert(app.Refresh(), gc.IsNil) 1726 c.Assert(app.MinUnits(), gc.Equals, minUnits) 1727 } 1728 1729 func (s *applicationSuite) TestApplicationUpdateSetMinUnitsWithLXDProfile(c *gc.C) { 1730 app := s.AddTestingApplication(c, "lxd-profile", s.AddTestingCharm(c, "lxd-profile")) 1731 1732 // Set minimum units for the application. 1733 minUnits := 2 1734 args := params.ApplicationUpdate{ 1735 ApplicationName: "lxd-profile", 1736 MinUnits: &minUnits, 1737 } 1738 err := s.applicationAPI.Update(args) 1739 c.Assert(err, jc.ErrorIsNil) 1740 1741 // Ensure the minimum number of units has been set. 1742 c.Assert(app.Refresh(), gc.IsNil) 1743 c.Assert(app.MinUnits(), gc.Equals, minUnits) 1744 } 1745 1746 func (s *applicationSuite) TestApplicationUpdateDoesNotSetMinUnitsWithLXDProfile(c *gc.C) { 1747 series := "quantal" 1748 repo := testcharms.RepoForSeries(series) 1749 ch := repo.CharmDir("lxd-profile-fail") 1750 ident := fmt.Sprintf("%s-%d", ch.Meta().Name, ch.Revision()) 1751 curl := charm.MustParseURL(fmt.Sprintf("local:%s/%s", series, ident)) 1752 storerepo, err := charmrepo.InferRepository( 1753 curl, 1754 charmrepo.NewCharmStoreParams{}, 1755 repo.Path()) 1756 c.Assert(err, jc.ErrorIsNil) 1757 _, err = jujutesting.PutCharm(s.State, curl, storerepo, false, false) 1758 c.Assert(err, gc.ErrorMatches, `invalid lxd-profile.yaml: contains device type "unix-disk"`) 1759 } 1760 1761 func (s *applicationSuite) TestApplicationUpdateSetMinUnitsError(c *gc.C) { 1762 app := s.AddTestingApplication(c, "dummy", s.AddTestingCharm(c, "dummy")) 1763 1764 // Set a negative minimum number of units for the application. 1765 minUnits := -1 1766 args := params.ApplicationUpdate{ 1767 ApplicationName: "dummy", 1768 MinUnits: &minUnits, 1769 } 1770 err := s.applicationAPI.Update(args) 1771 c.Assert(err, gc.ErrorMatches, 1772 `cannot set minimum units for application "dummy": cannot set a negative minimum number of units`) 1773 1774 // Ensure the minimum number of units has not been set. 1775 c.Assert(app.Refresh(), gc.IsNil) 1776 c.Assert(app.MinUnits(), gc.Equals, 0) 1777 } 1778 1779 func (s *applicationSuite) TestApplicationUpdateSetSettingsStrings(c *gc.C) { 1780 ch := s.AddTestingCharm(c, "dummy") 1781 app := s.AddTestingApplication(c, "dummy", ch) 1782 1783 // Update settings for the application. 1784 args := params.ApplicationUpdate{ 1785 ApplicationName: "dummy", 1786 SettingsStrings: map[string]string{"title": "s-title", "username": "s-user"}, 1787 } 1788 err := s.applicationAPI.Update(args) 1789 c.Assert(err, jc.ErrorIsNil) 1790 1791 // Ensure the settings have been correctly updated. 1792 expected := charm.Settings{"title": "s-title", "username": "s-user"} 1793 obtained, err := app.CharmConfig() 1794 c.Assert(err, jc.ErrorIsNil) 1795 c.Assert(obtained, gc.DeepEquals, s.combinedSettings(ch, expected)) 1796 } 1797 1798 func (s *applicationSuite) TestApplicationUpdateSetSettingsYAML(c *gc.C) { 1799 ch := s.AddTestingCharm(c, "dummy") 1800 app := s.AddTestingApplication(c, "dummy", ch) 1801 1802 // Update settings for the application. 1803 args := params.ApplicationUpdate{ 1804 ApplicationName: "dummy", 1805 SettingsYAML: "dummy:\n title: y-title\n username: y-user", 1806 } 1807 err := s.applicationAPI.Update(args) 1808 c.Assert(err, jc.ErrorIsNil) 1809 1810 // Ensure the settings have been correctly updated. 1811 expected := charm.Settings{"title": "y-title", "username": "y-user"} 1812 obtained, err := app.CharmConfig() 1813 c.Assert(err, jc.ErrorIsNil) 1814 c.Assert(obtained, gc.DeepEquals, s.combinedSettings(ch, expected)) 1815 } 1816 1817 func (s *applicationSuite) TestClientApplicationUpdateSetSettingsGetYAML(c *gc.C) { 1818 ch := s.AddTestingCharm(c, "dummy") 1819 app := s.AddTestingApplication(c, "dummy", ch) 1820 1821 // Update settings for the application. 1822 args := params.ApplicationUpdate{ 1823 ApplicationName: "dummy", 1824 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", 1825 } 1826 err := s.applicationAPI.Update(args) 1827 c.Assert(err, jc.ErrorIsNil) 1828 1829 // Ensure the settings have been correctly updated. 1830 expected := charm.Settings{"title": "y-title", "username": "y-user"} 1831 obtained, err := app.CharmConfig() 1832 c.Assert(err, jc.ErrorIsNil) 1833 c.Assert(obtained, gc.DeepEquals, s.combinedSettings(ch, expected)) 1834 } 1835 1836 func (s *applicationSuite) TestApplicationUpdateSetConstraints(c *gc.C) { 1837 app := s.AddTestingApplication(c, "dummy", s.AddTestingCharm(c, "dummy")) 1838 1839 // Update constraints for the application. 1840 cons, err := constraints.Parse("mem=4096", "cores=2") 1841 c.Assert(err, jc.ErrorIsNil) 1842 args := params.ApplicationUpdate{ 1843 ApplicationName: "dummy", 1844 Constraints: &cons, 1845 } 1846 err = s.applicationAPI.Update(args) 1847 c.Assert(err, jc.ErrorIsNil) 1848 1849 // Ensure the constraints have been correctly updated. 1850 obtained, err := app.Constraints() 1851 c.Assert(err, jc.ErrorIsNil) 1852 c.Assert(obtained, gc.DeepEquals, cons) 1853 } 1854 1855 func (s *applicationSuite) TestApplicationUpdateAllParams(c *gc.C) { 1856 s.deployApplicationForUpdateTests(c) 1857 curl, _ := s.UploadCharm(c, "precise/wordpress-3", "wordpress") 1858 err := application.AddCharmWithAuthorization(application.NewStateShim(s.State), params.AddCharmWithAuthorization{ 1859 URL: curl.String(), 1860 }) 1861 c.Assert(err, jc.ErrorIsNil) 1862 1863 // Update all the application attributes. 1864 minUnits := 3 1865 cons, err := constraints.Parse("mem=4096", "cores=2") 1866 c.Assert(err, jc.ErrorIsNil) 1867 args := params.ApplicationUpdate{ 1868 ApplicationName: "application", 1869 CharmURL: curl.String(), 1870 ForceCharmURL: true, 1871 MinUnits: &minUnits, 1872 SettingsStrings: map[string]string{"blog-title": "string-title"}, 1873 SettingsYAML: "application:\n blog-title: yaml-title\n", 1874 Constraints: &cons, 1875 } 1876 err = s.applicationAPI.Update(args) 1877 c.Assert(err, jc.ErrorIsNil) 1878 1879 // Ensure the application has been correctly updated. 1880 app, err := s.State.Application("application") 1881 c.Assert(err, jc.ErrorIsNil) 1882 1883 // Check the charm. 1884 ch, force, err := app.Charm() 1885 c.Assert(err, jc.ErrorIsNil) 1886 c.Assert(ch.URL().String(), gc.Equals, curl.String()) 1887 c.Assert(force, jc.IsTrue) 1888 1889 // Check the minimum number of units. 1890 c.Assert(app.MinUnits(), gc.Equals, minUnits) 1891 1892 // Check the settings: also ensure the YAML settings take precedence 1893 // over strings ones. 1894 expectedSettings := charm.Settings{"blog-title": "yaml-title"} 1895 obtainedSettings, err := app.CharmConfig() 1896 c.Assert(err, jc.ErrorIsNil) 1897 c.Assert(obtainedSettings, gc.DeepEquals, expectedSettings) 1898 1899 // Check the constraints. 1900 obtainedConstraints, err := app.Constraints() 1901 c.Assert(err, jc.ErrorIsNil) 1902 c.Assert(obtainedConstraints, gc.DeepEquals, cons) 1903 } 1904 1905 func (s *applicationSuite) TestApplicationUpdateNoParams(c *gc.C) { 1906 s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 1907 1908 // Calling Update with no parameters set is a no-op. 1909 args := params.ApplicationUpdate{ApplicationName: "wordpress"} 1910 err := s.applicationAPI.Update(args) 1911 c.Assert(err, jc.ErrorIsNil) 1912 } 1913 1914 func (s *applicationSuite) TestApplicationUpdateNoApplication(c *gc.C) { 1915 err := s.applicationAPI.Update(params.ApplicationUpdate{}) 1916 c.Assert(err, gc.ErrorMatches, `"" is not a valid application name`) 1917 } 1918 1919 func (s *applicationSuite) TestApplicationUpdateInvalidApplication(c *gc.C) { 1920 args := params.ApplicationUpdate{ApplicationName: "no-such-application"} 1921 err := s.applicationAPI.Update(args) 1922 c.Assert(err, gc.ErrorMatches, `application "no-such-application" not found`) 1923 } 1924 1925 var ( 1926 validSetTestValue = "a value with spaces\nand newline\nand UTF-8 characters: \U0001F604 / \U0001F44D" 1927 ) 1928 1929 func (s *applicationSuite) TestApplicationSet(c *gc.C) { 1930 ch := s.AddTestingCharm(c, "dummy") 1931 dummy := s.AddTestingApplication(c, "dummy", ch) 1932 1933 err := s.applicationAPI.Set(params.ApplicationSet{ApplicationName: "dummy", Options: map[string]string{ 1934 "title": "foobar", 1935 "username": validSetTestValue, 1936 }}) 1937 c.Assert(err, jc.ErrorIsNil) 1938 settings, err := dummy.CharmConfig() 1939 c.Assert(err, jc.ErrorIsNil) 1940 c.Assert(settings, gc.DeepEquals, s.combinedSettings(ch, charm.Settings{ 1941 "title": "foobar", 1942 "username": validSetTestValue, 1943 })) 1944 1945 err = s.applicationAPI.Set(params.ApplicationSet{ApplicationName: "dummy", Options: map[string]string{ 1946 "title": "barfoo", 1947 "username": "", 1948 }}) 1949 c.Assert(err, jc.ErrorIsNil) 1950 settings, err = dummy.CharmConfig() 1951 c.Assert(err, jc.ErrorIsNil) 1952 c.Assert(settings, gc.DeepEquals, s.combinedSettings(ch, charm.Settings{ 1953 "title": "barfoo", 1954 "username": "", 1955 })) 1956 } 1957 1958 func (s *applicationSuite) assertApplicationSetBlocked(c *gc.C, dummy *state.Application, msg string) { 1959 err := s.applicationAPI.Set(params.ApplicationSet{ 1960 ApplicationName: "dummy", 1961 Options: map[string]string{ 1962 "title": "foobar", 1963 "username": validSetTestValue}}) 1964 s.AssertBlocked(c, err, msg) 1965 } 1966 1967 func (s *applicationSuite) assertApplicationSet(c *gc.C, dummy *state.Application) { 1968 err := s.applicationAPI.Set(params.ApplicationSet{ 1969 ApplicationName: "dummy", 1970 Options: map[string]string{ 1971 "title": "foobar", 1972 "username": validSetTestValue}}) 1973 c.Assert(err, jc.ErrorIsNil) 1974 settings, err := dummy.CharmConfig() 1975 c.Assert(err, jc.ErrorIsNil) 1976 ch, _, err := dummy.Charm() 1977 c.Assert(err, jc.ErrorIsNil) 1978 c.Assert(settings, gc.DeepEquals, s.combinedSettings(ch, charm.Settings{ 1979 "title": "foobar", 1980 "username": validSetTestValue, 1981 })) 1982 } 1983 1984 func (s *applicationSuite) TestBlockDestroyApplicationSet(c *gc.C) { 1985 dummy := s.AddTestingApplication(c, "dummy", s.AddTestingCharm(c, "dummy")) 1986 s.BlockDestroyModel(c, "TestBlockDestroyApplicationSet") 1987 s.assertApplicationSet(c, dummy) 1988 } 1989 1990 func (s *applicationSuite) TestBlockRemoveApplicationSet(c *gc.C) { 1991 dummy := s.AddTestingApplication(c, "dummy", s.AddTestingCharm(c, "dummy")) 1992 s.BlockRemoveObject(c, "TestBlockRemoveApplicationSet") 1993 s.assertApplicationSet(c, dummy) 1994 } 1995 1996 func (s *applicationSuite) TestBlockChangesApplicationSet(c *gc.C) { 1997 dummy := s.AddTestingApplication(c, "dummy", s.AddTestingCharm(c, "dummy")) 1998 s.BlockAllChanges(c, "TestBlockChangesApplicationSet") 1999 s.assertApplicationSetBlocked(c, dummy, "TestBlockChangesApplicationSet") 2000 } 2001 2002 func (s *applicationSuite) TestServerUnset(c *gc.C) { 2003 ch := s.AddTestingCharm(c, "dummy") 2004 dummy := s.AddTestingApplication(c, "dummy", ch) 2005 2006 err := s.applicationAPI.Set(params.ApplicationSet{ApplicationName: "dummy", Options: map[string]string{ 2007 "title": "foobar", 2008 "username": "user name", 2009 }}) 2010 c.Assert(err, jc.ErrorIsNil) 2011 settings, err := dummy.CharmConfig() 2012 c.Assert(err, jc.ErrorIsNil) 2013 c.Assert(settings, gc.DeepEquals, s.combinedSettings(ch, charm.Settings{ 2014 "title": "foobar", 2015 "username": "user name", 2016 })) 2017 2018 err = s.applicationAPI.Unset(params.ApplicationUnset{ApplicationName: "dummy", Options: []string{"username"}}) 2019 c.Assert(err, jc.ErrorIsNil) 2020 settings, err = dummy.CharmConfig() 2021 c.Assert(err, jc.ErrorIsNil) 2022 c.Assert(settings, gc.DeepEquals, s.combinedSettings(ch, charm.Settings{ 2023 "title": "foobar", 2024 })) 2025 } 2026 2027 func (s *applicationSuite) setupServerUnsetBlocked(c *gc.C) *state.Application { 2028 dummy := s.AddTestingApplication(c, "dummy", s.AddTestingCharm(c, "dummy")) 2029 2030 err := s.applicationAPI.Set(params.ApplicationSet{ 2031 ApplicationName: "dummy", 2032 Options: map[string]string{ 2033 "title": "foobar", 2034 "username": "user name", 2035 }}) 2036 c.Assert(err, jc.ErrorIsNil) 2037 settings, err := dummy.CharmConfig() 2038 c.Assert(err, jc.ErrorIsNil) 2039 ch, _, err := dummy.Charm() 2040 c.Assert(err, jc.ErrorIsNil) 2041 c.Assert(settings, gc.DeepEquals, s.combinedSettings(ch, charm.Settings{ 2042 "title": "foobar", 2043 "username": "user name", 2044 })) 2045 return dummy 2046 } 2047 2048 func (s *applicationSuite) assertServerUnset(c *gc.C, dummy *state.Application) { 2049 err := s.applicationAPI.Unset(params.ApplicationUnset{ 2050 ApplicationName: "dummy", 2051 Options: []string{"username"}, 2052 }) 2053 c.Assert(err, jc.ErrorIsNil) 2054 settings, err := dummy.CharmConfig() 2055 c.Assert(err, jc.ErrorIsNil) 2056 ch, _, err := dummy.Charm() 2057 c.Assert(err, jc.ErrorIsNil) 2058 c.Assert(settings, gc.DeepEquals, s.combinedSettings(ch, charm.Settings{ 2059 "title": "foobar", 2060 })) 2061 } 2062 2063 func (s *applicationSuite) assertServerUnsetBlocked(c *gc.C, dummy *state.Application, msg string) { 2064 err := s.applicationAPI.Unset(params.ApplicationUnset{ 2065 ApplicationName: "dummy", 2066 Options: []string{"username"}, 2067 }) 2068 s.AssertBlocked(c, err, msg) 2069 } 2070 2071 func (s *applicationSuite) TestBlockDestroyServerUnset(c *gc.C) { 2072 dummy := s.setupServerUnsetBlocked(c) 2073 s.BlockDestroyModel(c, "TestBlockDestroyServerUnset") 2074 s.assertServerUnset(c, dummy) 2075 } 2076 2077 func (s *applicationSuite) TestBlockRemoveServerUnset(c *gc.C) { 2078 dummy := s.setupServerUnsetBlocked(c) 2079 s.BlockRemoveObject(c, "TestBlockRemoveServerUnset") 2080 s.assertServerUnset(c, dummy) 2081 } 2082 2083 func (s *applicationSuite) TestBlockChangesServerUnset(c *gc.C) { 2084 dummy := s.setupServerUnsetBlocked(c) 2085 s.BlockAllChanges(c, "TestBlockChangesServerUnset") 2086 s.assertServerUnsetBlocked(c, dummy, "TestBlockChangesServerUnset") 2087 } 2088 2089 var clientAddApplicationUnitsTests = []struct { 2090 about string 2091 application string // if not set, defaults to 'dummy' 2092 numUnits int 2093 expected []string 2094 to string 2095 err string 2096 }{ 2097 { 2098 about: "returns unit names", 2099 numUnits: 3, 2100 expected: []string{"dummy/0", "dummy/1", "dummy/2"}, 2101 }, 2102 { 2103 about: "fails trying to add zero units", 2104 err: "must add at least one unit", 2105 }, 2106 { 2107 // Note: chained-state, we add 1 unit here, but the 3 units 2108 // from the first condition still exist 2109 about: "force the unit onto bootstrap machine", 2110 numUnits: 1, 2111 expected: []string{"dummy/3"}, 2112 to: "0", 2113 }, 2114 { 2115 about: "unknown application name", 2116 application: "unknown-application", 2117 numUnits: 1, 2118 err: `application "unknown-application" not found`, 2119 }, 2120 } 2121 2122 func (s *applicationSuite) TestClientAddApplicationUnits(c *gc.C) { 2123 s.AddTestingApplication(c, "dummy", s.AddTestingCharm(c, "dummy")) 2124 for i, t := range clientAddApplicationUnitsTests { 2125 c.Logf("test %d. %s", i, t.about) 2126 applicationName := t.application 2127 if applicationName == "" { 2128 applicationName = "dummy" 2129 } 2130 args := params.AddApplicationUnits{ 2131 ApplicationName: applicationName, 2132 NumUnits: t.numUnits, 2133 } 2134 if t.to != "" { 2135 args.Placement = []*instance.Placement{instance.MustParsePlacement(t.to)} 2136 } 2137 result, err := s.applicationAPI.AddUnits(args) 2138 if t.err != "" { 2139 c.Assert(err, gc.ErrorMatches, t.err) 2140 continue 2141 } 2142 c.Assert(err, jc.ErrorIsNil) 2143 c.Assert(result.Units, gc.DeepEquals, t.expected) 2144 } 2145 // Test that we actually assigned the unit to machine 0 2146 forcedUnit, err := s.BackingState.Unit("dummy/3") 2147 c.Assert(err, jc.ErrorIsNil) 2148 assignedMachine, err := forcedUnit.AssignedMachineId() 2149 c.Assert(err, jc.ErrorIsNil) 2150 c.Assert(assignedMachine, gc.Equals, "0") 2151 } 2152 2153 func (s *applicationSuite) TestAddApplicationUnitsToNewContainer(c *gc.C) { 2154 app := s.AddTestingApplication(c, "dummy", s.AddTestingCharm(c, "dummy")) 2155 machine, err := s.State.AddMachine("quantal", state.JobHostUnits) 2156 c.Assert(err, jc.ErrorIsNil) 2157 2158 _, err = s.applicationAPI.AddUnits(params.AddApplicationUnits{ 2159 ApplicationName: "dummy", 2160 NumUnits: 1, 2161 Placement: []*instance.Placement{instance.MustParsePlacement("lxd:" + machine.Id())}, 2162 }) 2163 c.Assert(err, jc.ErrorIsNil) 2164 2165 units, err := app.AllUnits() 2166 c.Assert(err, jc.ErrorIsNil) 2167 mid, err := units[0].AssignedMachineId() 2168 c.Assert(err, jc.ErrorIsNil) 2169 c.Assert(mid, gc.Equals, machine.Id()+"/lxd/0") 2170 } 2171 2172 var addApplicationUnitTests = []struct { 2173 about string 2174 application string // if not set, defaults to 'dummy' 2175 expected []string 2176 machineIds []string 2177 placement []*instance.Placement 2178 err string 2179 }{ 2180 { 2181 about: "valid placement directives", 2182 expected: []string{"dummy/0"}, 2183 placement: []*instance.Placement{{"deadbeef-0bad-400d-8000-4b1d0d06f00d", "valid"}}, 2184 machineIds: []string{"1"}, 2185 }, { 2186 about: "direct machine assignment placement directive", 2187 expected: []string{"dummy/1", "dummy/2"}, 2188 placement: []*instance.Placement{{"#", "1"}, {"lxd", "1"}}, 2189 machineIds: []string{"1", "1/lxd/0"}, 2190 }, { 2191 about: "invalid placement directive", 2192 err: ".* invalid placement is invalid", 2193 expected: []string{"dummy/3"}, 2194 placement: []*instance.Placement{{"deadbeef-0bad-400d-8000-4b1d0d06f00d", "invalid"}}, 2195 }, 2196 } 2197 2198 func (s *applicationSuite) TestAddApplicationUnits(c *gc.C) { 2199 s.AddTestingApplication(c, "dummy", s.AddTestingCharm(c, "dummy")) 2200 // Add a machine for the units to be placed on. 2201 _, err := s.State.AddMachine("quantal", state.JobHostUnits) 2202 c.Assert(err, jc.ErrorIsNil) 2203 for i, t := range addApplicationUnitTests { 2204 c.Logf("test %d. %s", i, t.about) 2205 applicationName := t.application 2206 if applicationName == "" { 2207 applicationName = "dummy" 2208 } 2209 result, err := s.applicationAPI.AddUnits(params.AddApplicationUnits{ 2210 ApplicationName: applicationName, 2211 NumUnits: len(t.expected), 2212 Placement: t.placement, 2213 }) 2214 if t.err != "" { 2215 c.Assert(err, gc.ErrorMatches, t.err) 2216 continue 2217 } 2218 c.Assert(err, jc.ErrorIsNil) 2219 c.Assert(result.Units, gc.DeepEquals, t.expected) 2220 for i, unitName := range result.Units { 2221 u, err := s.BackingState.Unit(unitName) 2222 c.Assert(err, jc.ErrorIsNil) 2223 assignedMachine, err := u.AssignedMachineId() 2224 c.Assert(err, jc.ErrorIsNil) 2225 c.Assert(assignedMachine, gc.Equals, t.machineIds[i]) 2226 } 2227 } 2228 } 2229 2230 func (s *applicationSuite) assertAddApplicationUnits(c *gc.C) { 2231 result, err := s.applicationAPI.AddUnits(params.AddApplicationUnits{ 2232 ApplicationName: "dummy", 2233 NumUnits: 3, 2234 }) 2235 c.Assert(err, jc.ErrorIsNil) 2236 c.Assert(result.Units, gc.DeepEquals, []string{"dummy/0", "dummy/1", "dummy/2"}) 2237 2238 // Test that we actually assigned the unit to machine 0 2239 forcedUnit, err := s.BackingState.Unit("dummy/0") 2240 c.Assert(err, jc.ErrorIsNil) 2241 assignedMachine, err := forcedUnit.AssignedMachineId() 2242 c.Assert(err, jc.ErrorIsNil) 2243 c.Assert(assignedMachine, gc.Equals, "0") 2244 } 2245 2246 func (s *applicationSuite) TestApplicationCharmRelations(c *gc.C) { 2247 s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 2248 s.AddTestingApplication(c, "logging", s.AddTestingCharm(c, "logging")) 2249 eps, err := s.State.InferEndpoints("logging", "wordpress") 2250 c.Assert(err, jc.ErrorIsNil) 2251 _, err = s.State.AddRelation(eps...) 2252 c.Assert(err, jc.ErrorIsNil) 2253 2254 _, err = s.applicationAPI.CharmRelations(params.ApplicationCharmRelations{"blah"}) 2255 c.Assert(err, gc.ErrorMatches, `application "blah" not found`) 2256 2257 result, err := s.applicationAPI.CharmRelations(params.ApplicationCharmRelations{"wordpress"}) 2258 c.Assert(err, jc.ErrorIsNil) 2259 c.Assert(result.CharmRelations, gc.DeepEquals, []string{ 2260 "cache", "db", "juju-info", "logging-dir", "monitoring-port", "url", 2261 }) 2262 } 2263 2264 func (s *applicationSuite) assertAddApplicationUnitsBlocked(c *gc.C, msg string) { 2265 _, err := s.applicationAPI.AddUnits(params.AddApplicationUnits{ 2266 ApplicationName: "dummy", 2267 NumUnits: 3, 2268 }) 2269 s.AssertBlocked(c, err, msg) 2270 } 2271 2272 func (s *applicationSuite) TestBlockDestroyAddApplicationUnits(c *gc.C) { 2273 s.AddTestingApplication(c, "dummy", s.AddTestingCharm(c, "dummy")) 2274 s.BlockDestroyModel(c, "TestBlockDestroyAddApplicationUnits") 2275 s.assertAddApplicationUnits(c) 2276 } 2277 2278 func (s *applicationSuite) TestBlockRemoveAddApplicationUnits(c *gc.C) { 2279 s.AddTestingApplication(c, "dummy", s.AddTestingCharm(c, "dummy")) 2280 s.BlockRemoveObject(c, "TestBlockRemoveAddApplicationUnits") 2281 s.assertAddApplicationUnits(c) 2282 } 2283 2284 func (s *applicationSuite) TestBlockChangeAddApplicationUnits(c *gc.C) { 2285 s.AddTestingApplication(c, "dummy", s.AddTestingCharm(c, "dummy")) 2286 s.BlockAllChanges(c, "TestBlockChangeAddApplicationUnits") 2287 s.assertAddApplicationUnitsBlocked(c, "TestBlockChangeAddApplicationUnits") 2288 } 2289 2290 func (s *applicationSuite) TestAddUnitToMachineNotFound(c *gc.C) { 2291 s.AddTestingApplication(c, "dummy", s.AddTestingCharm(c, "dummy")) 2292 _, err := s.applicationAPI.AddUnits(params.AddApplicationUnits{ 2293 ApplicationName: "dummy", 2294 NumUnits: 3, 2295 Placement: []*instance.Placement{instance.MustParsePlacement("42")}, 2296 }) 2297 c.Assert(err, gc.ErrorMatches, `acquiring machine to host unit "dummy/0": machine 42 not found`) 2298 } 2299 2300 func (s *applicationSuite) TestApplicationExpose(c *gc.C) { 2301 charm := s.AddTestingCharm(c, "dummy") 2302 applicationNames := []string{"dummy-application", "exposed-application"} 2303 apps := make([]*state.Application, len(applicationNames)) 2304 var err error 2305 for i, name := range applicationNames { 2306 apps[i] = s.AddTestingApplication(c, name, charm) 2307 c.Assert(apps[i].IsExposed(), jc.IsFalse) 2308 } 2309 err = apps[1].SetExposed() 2310 c.Assert(err, jc.ErrorIsNil) 2311 c.Assert(apps[1].IsExposed(), jc.IsTrue) 2312 for i, t := range applicationExposeTests { 2313 c.Logf("test %d. %s", i, t.about) 2314 err = s.applicationAPI.Expose(params.ApplicationExpose{t.application}) 2315 if t.err != "" { 2316 c.Assert(err, gc.ErrorMatches, t.err) 2317 } else { 2318 c.Assert(err, jc.ErrorIsNil) 2319 app, err := s.State.Application(t.application) 2320 c.Assert(err, jc.ErrorIsNil) 2321 c.Assert(app.IsExposed(), gc.Equals, t.exposed) 2322 } 2323 } 2324 } 2325 2326 func (s *applicationSuite) setupApplicationExpose(c *gc.C) { 2327 charm := s.AddTestingCharm(c, "dummy") 2328 applicationNames := []string{"dummy-application", "exposed-application"} 2329 apps := make([]*state.Application, len(applicationNames)) 2330 var err error 2331 for i, name := range applicationNames { 2332 apps[i] = s.AddTestingApplication(c, name, charm) 2333 c.Assert(apps[i].IsExposed(), jc.IsFalse) 2334 } 2335 err = apps[1].SetExposed() 2336 c.Assert(err, jc.ErrorIsNil) 2337 c.Assert(apps[1].IsExposed(), jc.IsTrue) 2338 } 2339 2340 var applicationExposeTests = []struct { 2341 about string 2342 application string 2343 err string 2344 exposed bool 2345 }{ 2346 { 2347 about: "unknown application name", 2348 application: "unknown-application", 2349 err: `application "unknown-application" not found`, 2350 }, 2351 { 2352 about: "expose a application", 2353 application: "dummy-application", 2354 exposed: true, 2355 }, 2356 { 2357 about: "expose an already exposed application", 2358 application: "exposed-application", 2359 exposed: true, 2360 }, 2361 } 2362 2363 func (s *applicationSuite) assertApplicationExpose(c *gc.C) { 2364 for i, t := range applicationExposeTests { 2365 c.Logf("test %d. %s", i, t.about) 2366 err := s.applicationAPI.Expose(params.ApplicationExpose{t.application}) 2367 if t.err != "" { 2368 c.Assert(err, gc.ErrorMatches, t.err) 2369 } else { 2370 c.Assert(err, jc.ErrorIsNil) 2371 application, err := s.State.Application(t.application) 2372 c.Assert(err, jc.ErrorIsNil) 2373 c.Assert(application.IsExposed(), gc.Equals, t.exposed) 2374 } 2375 } 2376 } 2377 2378 func (s *applicationSuite) assertApplicationExposeBlocked(c *gc.C, msg string) { 2379 for i, t := range applicationExposeTests { 2380 c.Logf("test %d. %s", i, t.about) 2381 err := s.applicationAPI.Expose(params.ApplicationExpose{t.application}) 2382 s.AssertBlocked(c, err, msg) 2383 } 2384 } 2385 2386 func (s *applicationSuite) TestBlockDestroyApplicationExpose(c *gc.C) { 2387 s.setupApplicationExpose(c) 2388 s.BlockDestroyModel(c, "TestBlockDestroyApplicationExpose") 2389 s.assertApplicationExpose(c) 2390 } 2391 2392 func (s *applicationSuite) TestBlockRemoveApplicationExpose(c *gc.C) { 2393 s.setupApplicationExpose(c) 2394 s.BlockRemoveObject(c, "TestBlockRemoveApplicationExpose") 2395 s.assertApplicationExpose(c) 2396 } 2397 2398 func (s *applicationSuite) TestBlockChangesApplicationExpose(c *gc.C) { 2399 s.setupApplicationExpose(c) 2400 s.BlockAllChanges(c, "TestBlockChangesApplicationExpose") 2401 s.assertApplicationExposeBlocked(c, "TestBlockChangesApplicationExpose") 2402 } 2403 2404 var applicationUnexposeTests = []struct { 2405 about string 2406 application string 2407 err string 2408 initial bool 2409 expected bool 2410 }{ 2411 { 2412 about: "unknown application name", 2413 application: "unknown-application", 2414 err: `application "unknown-application" not found`, 2415 }, 2416 { 2417 about: "unexpose a application", 2418 application: "dummy-application", 2419 initial: true, 2420 expected: false, 2421 }, 2422 { 2423 about: "unexpose an already unexposed application", 2424 application: "dummy-application", 2425 initial: false, 2426 expected: false, 2427 }, 2428 } 2429 2430 func (s *applicationSuite) TestApplicationUnexpose(c *gc.C) { 2431 charm := s.AddTestingCharm(c, "dummy") 2432 for i, t := range applicationUnexposeTests { 2433 c.Logf("test %d. %s", i, t.about) 2434 app := s.AddTestingApplication(c, "dummy-application", charm) 2435 if t.initial { 2436 app.SetExposed() 2437 } 2438 c.Assert(app.IsExposed(), gc.Equals, t.initial) 2439 err := s.applicationAPI.Unexpose(params.ApplicationUnexpose{t.application}) 2440 if t.err == "" { 2441 c.Assert(err, jc.ErrorIsNil) 2442 app.Refresh() 2443 c.Assert(app.IsExposed(), gc.Equals, t.expected) 2444 } else { 2445 c.Assert(err, gc.ErrorMatches, t.err) 2446 } 2447 err = app.Destroy() 2448 c.Assert(err, jc.ErrorIsNil) 2449 } 2450 } 2451 2452 func (s *applicationSuite) setupApplicationUnexpose(c *gc.C) *state.Application { 2453 charm := s.AddTestingCharm(c, "dummy") 2454 app := s.AddTestingApplication(c, "dummy-application", charm) 2455 app.SetExposed() 2456 c.Assert(app.IsExposed(), gc.Equals, true) 2457 return app 2458 } 2459 2460 func (s *applicationSuite) assertApplicationUnexpose(c *gc.C, app *state.Application) { 2461 err := s.applicationAPI.Unexpose(params.ApplicationUnexpose{"dummy-application"}) 2462 c.Assert(err, jc.ErrorIsNil) 2463 app.Refresh() 2464 c.Assert(app.IsExposed(), gc.Equals, false) 2465 err = app.Destroy() 2466 c.Assert(err, jc.ErrorIsNil) 2467 } 2468 2469 func (s *applicationSuite) assertApplicationUnexposeBlocked(c *gc.C, app *state.Application, msg string) { 2470 err := s.applicationAPI.Unexpose(params.ApplicationUnexpose{"dummy-application"}) 2471 s.AssertBlocked(c, err, msg) 2472 err = app.Destroy() 2473 c.Assert(err, jc.ErrorIsNil) 2474 } 2475 2476 func (s *applicationSuite) TestBlockDestroyApplicationUnexpose(c *gc.C) { 2477 app := s.setupApplicationUnexpose(c) 2478 s.BlockDestroyModel(c, "TestBlockDestroyApplicationUnexpose") 2479 s.assertApplicationUnexpose(c, app) 2480 } 2481 2482 func (s *applicationSuite) TestBlockRemoveApplicationUnexpose(c *gc.C) { 2483 app := s.setupApplicationUnexpose(c) 2484 s.BlockRemoveObject(c, "TestBlockRemoveApplicationUnexpose") 2485 s.assertApplicationUnexpose(c, app) 2486 } 2487 2488 func (s *applicationSuite) TestBlockChangesApplicationUnexpose(c *gc.C) { 2489 app := s.setupApplicationUnexpose(c) 2490 s.BlockAllChanges(c, "TestBlockChangesApplicationUnexpose") 2491 s.assertApplicationUnexposeBlocked(c, app, "TestBlockChangesApplicationUnexpose") 2492 } 2493 2494 var applicationDestroyTests = []struct { 2495 about string 2496 application string 2497 err string 2498 }{ 2499 { 2500 about: "unknown application name", 2501 application: "unknown-application", 2502 err: `application "unknown-application" not found`, 2503 }, 2504 { 2505 about: "destroy an application", 2506 application: "dummy-application", 2507 }, 2508 { 2509 about: "destroy an already destroyed application", 2510 application: "dummy-application", 2511 err: `application "dummy-application" not found`, 2512 }, 2513 } 2514 2515 func (s *applicationSuite) TestApplicationDestroy(c *gc.C) { 2516 s.AddTestingApplication(c, "dummy-application", s.AddTestingCharm(c, "dummy")) 2517 _, err := s.State.AddRemoteApplication(state.AddRemoteApplicationParams{ 2518 Name: "remote-application", 2519 SourceModel: s.Model.ModelTag(), 2520 Token: "t0", 2521 }) 2522 c.Assert(err, jc.ErrorIsNil) 2523 2524 for i, t := range applicationDestroyTests { 2525 c.Logf("test %d. %s", i, t.about) 2526 err := s.applicationAPI.Destroy(params.ApplicationDestroy{t.application}) 2527 if t.err != "" { 2528 c.Assert(err, gc.ErrorMatches, t.err) 2529 } else { 2530 c.Assert(err, jc.ErrorIsNil) 2531 } 2532 } 2533 2534 // Now do Destroy on an application with units. Destroy will 2535 // cause the application to be not-Alive, but will not remove its 2536 // document. 2537 s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 2538 applicationName := "wordpress" 2539 app, err := s.State.Application(applicationName) 2540 c.Assert(err, jc.ErrorIsNil) 2541 err = s.applicationAPI.Destroy(params.ApplicationDestroy{applicationName}) 2542 c.Assert(err, jc.ErrorIsNil) 2543 err = app.Refresh() 2544 c.Assert(err, jc.Satisfies, errors.IsNotFound) 2545 } 2546 2547 func assertLife(c *gc.C, entity state.Living, life state.Life) { 2548 err := entity.Refresh() 2549 c.Assert(err, jc.ErrorIsNil) 2550 c.Assert(entity.Life(), gc.Equals, life) 2551 } 2552 2553 func (s *applicationSuite) TestBlockApplicationDestroy(c *gc.C) { 2554 s.AddTestingApplication(c, "dummy-application", s.AddTestingCharm(c, "dummy")) 2555 2556 // block remove-objects 2557 s.BlockRemoveObject(c, "TestBlockApplicationDestroy") 2558 err := s.applicationAPI.Destroy(params.ApplicationDestroy{"dummy-application"}) 2559 s.AssertBlocked(c, err, "TestBlockApplicationDestroy") 2560 // Tests may have invalid application names. 2561 app, err := s.State.Application("dummy-application") 2562 if err == nil { 2563 // For valid application names, check that application is alive :-) 2564 assertLife(c, app, state.Alive) 2565 } 2566 } 2567 2568 func (s *applicationSuite) TestDestroyPrincipalUnits(c *gc.C) { 2569 wordpress := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 2570 units := make([]*state.Unit, 5) 2571 for i := range units { 2572 unit, err := wordpress.AddUnit(state.AddUnitParams{}) 2573 c.Assert(err, jc.ErrorIsNil) 2574 unit.AssignToNewMachine() 2575 c.Assert(err, jc.ErrorIsNil) 2576 now := time.Now() 2577 sInfo := status.StatusInfo{ 2578 Status: status.Idle, 2579 Message: "", 2580 Since: &now, 2581 } 2582 err = unit.SetAgentStatus(sInfo) 2583 c.Assert(err, jc.ErrorIsNil) 2584 units[i] = unit 2585 } 2586 s.assertDestroyPrincipalUnits(c, units) 2587 } 2588 2589 func (s *applicationSuite) TestDestroySubordinateUnits(c *gc.C) { 2590 wordpress := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 2591 wordpress0, err := wordpress.AddUnit(state.AddUnitParams{}) 2592 c.Assert(err, jc.ErrorIsNil) 2593 s.AddTestingApplication(c, "logging", s.AddTestingCharm(c, "logging")) 2594 eps, err := s.State.InferEndpoints("logging", "wordpress") 2595 c.Assert(err, jc.ErrorIsNil) 2596 rel, err := s.State.AddRelation(eps...) 2597 c.Assert(err, jc.ErrorIsNil) 2598 ru, err := rel.Unit(wordpress0) 2599 c.Assert(err, jc.ErrorIsNil) 2600 err = ru.EnterScope(nil) 2601 c.Assert(err, jc.ErrorIsNil) 2602 logging0, err := s.State.Unit("logging/0") 2603 c.Assert(err, jc.ErrorIsNil) 2604 2605 // Try to destroy the subordinate alone; check it fails. 2606 err = s.applicationAPI.DestroyUnits(params.DestroyApplicationUnits{ 2607 UnitNames: []string{"logging/0"}, 2608 }) 2609 c.Assert(err, gc.ErrorMatches, `no units were destroyed: unit "logging/0" is a subordinate`) 2610 assertLife(c, logging0, state.Alive) 2611 2612 s.assertDestroySubordinateUnits(c, wordpress0, logging0) 2613 } 2614 2615 func (s *applicationSuite) assertDestroyPrincipalUnits(c *gc.C, units []*state.Unit) { 2616 // Destroy 2 of them; check they become Dying. 2617 err := s.applicationAPI.DestroyUnits(params.DestroyApplicationUnits{ 2618 UnitNames: []string{"wordpress/0", "wordpress/1"}, 2619 }) 2620 c.Assert(err, jc.ErrorIsNil) 2621 assertLife(c, units[0], state.Dying) 2622 assertLife(c, units[1], state.Dying) 2623 2624 // Try to destroy an Alive one and a Dying one; check 2625 // it destroys the Alive one and ignores the Dying one. 2626 err = s.applicationAPI.DestroyUnits(params.DestroyApplicationUnits{ 2627 UnitNames: []string{"wordpress/2", "wordpress/0"}, 2628 }) 2629 c.Assert(err, jc.ErrorIsNil) 2630 assertLife(c, units[2], state.Dying) 2631 2632 // Try to destroy an Alive one along with a nonexistent one; check that 2633 // the valid instruction is followed but the invalid one is warned about. 2634 err = s.applicationAPI.DestroyUnits(params.DestroyApplicationUnits{ 2635 UnitNames: []string{"boojum/123", "wordpress/3"}, 2636 }) 2637 c.Assert(err, gc.ErrorMatches, `some units were not destroyed: unit "boojum/123" does not exist`) 2638 assertLife(c, units[3], state.Dying) 2639 2640 // Make one Dead, and destroy an Alive one alongside it; check no errors. 2641 wp0, err := s.State.Unit("wordpress/0") 2642 c.Assert(err, jc.ErrorIsNil) 2643 err = wp0.EnsureDead() 2644 c.Assert(err, jc.ErrorIsNil) 2645 err = s.applicationAPI.DestroyUnits(params.DestroyApplicationUnits{ 2646 UnitNames: []string{"wordpress/0", "wordpress/4"}, 2647 }) 2648 c.Assert(err, jc.ErrorIsNil) 2649 assertLife(c, units[0], state.Dead) 2650 assertLife(c, units[4], state.Dying) 2651 } 2652 2653 func (s *applicationSuite) setupDestroyPrincipalUnits(c *gc.C) []*state.Unit { 2654 units := make([]*state.Unit, 5) 2655 wordpress := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 2656 for i := range units { 2657 unit, err := wordpress.AddUnit(state.AddUnitParams{}) 2658 c.Assert(err, jc.ErrorIsNil) 2659 err = unit.AssignToNewMachine() 2660 c.Assert(err, jc.ErrorIsNil) 2661 now := time.Now() 2662 sInfo := status.StatusInfo{ 2663 Status: status.Idle, 2664 Message: "", 2665 Since: &now, 2666 } 2667 err = unit.SetAgentStatus(sInfo) 2668 c.Assert(err, jc.ErrorIsNil) 2669 units[i] = unit 2670 } 2671 return units 2672 } 2673 2674 func (s *applicationSuite) assertBlockedErrorAndLiveliness( 2675 c *gc.C, 2676 err error, 2677 msg string, 2678 living1 state.Living, 2679 living2 state.Living, 2680 living3 state.Living, 2681 living4 state.Living, 2682 ) { 2683 s.AssertBlocked(c, err, msg) 2684 assertLife(c, living1, state.Alive) 2685 assertLife(c, living2, state.Alive) 2686 assertLife(c, living3, state.Alive) 2687 assertLife(c, living4, state.Alive) 2688 } 2689 2690 func (s *applicationSuite) TestBlockChangesDestroyPrincipalUnits(c *gc.C) { 2691 units := s.setupDestroyPrincipalUnits(c) 2692 s.BlockAllChanges(c, "TestBlockChangesDestroyPrincipalUnits") 2693 err := s.applicationAPI.DestroyUnits(params.DestroyApplicationUnits{ 2694 UnitNames: []string{"wordpress/0", "wordpress/1"}, 2695 }) 2696 s.assertBlockedErrorAndLiveliness(c, err, "TestBlockChangesDestroyPrincipalUnits", units[0], units[1], units[2], units[3]) 2697 } 2698 2699 func (s *applicationSuite) TestBlockRemoveDestroyPrincipalUnits(c *gc.C) { 2700 units := s.setupDestroyPrincipalUnits(c) 2701 s.BlockRemoveObject(c, "TestBlockRemoveDestroyPrincipalUnits") 2702 err := s.applicationAPI.DestroyUnits(params.DestroyApplicationUnits{ 2703 UnitNames: []string{"wordpress/0", "wordpress/1"}, 2704 }) 2705 s.assertBlockedErrorAndLiveliness(c, err, "TestBlockRemoveDestroyPrincipalUnits", units[0], units[1], units[2], units[3]) 2706 } 2707 2708 func (s *applicationSuite) TestBlockDestroyDestroyPrincipalUnits(c *gc.C) { 2709 units := s.setupDestroyPrincipalUnits(c) 2710 s.BlockDestroyModel(c, "TestBlockDestroyDestroyPrincipalUnits") 2711 err := s.applicationAPI.DestroyUnits(params.DestroyApplicationUnits{ 2712 UnitNames: []string{"wordpress/0", "wordpress/1"}, 2713 }) 2714 c.Assert(err, jc.ErrorIsNil) 2715 assertLife(c, units[0], state.Dying) 2716 assertLife(c, units[1], state.Dying) 2717 } 2718 2719 func (s *applicationSuite) assertDestroySubordinateUnits(c *gc.C, wordpress0, logging0 *state.Unit) { 2720 // Try to destroy the principal and the subordinate together; check it warns 2721 // about the subordinate, but destroys the one it can. (The principal unit 2722 // agent will be responsible for destroying the subordinate.) 2723 err := s.applicationAPI.DestroyUnits(params.DestroyApplicationUnits{ 2724 UnitNames: []string{"wordpress/0", "logging/0"}, 2725 }) 2726 c.Assert(err, gc.ErrorMatches, `some units were not destroyed: unit "logging/0" is a subordinate`) 2727 assertLife(c, wordpress0, state.Dying) 2728 assertLife(c, logging0, state.Alive) 2729 } 2730 2731 func (s *applicationSuite) TestBlockRemoveDestroySubordinateUnits(c *gc.C) { 2732 wordpress := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 2733 wordpress0, err := wordpress.AddUnit(state.AddUnitParams{}) 2734 c.Assert(err, jc.ErrorIsNil) 2735 s.AddTestingApplication(c, "logging", s.AddTestingCharm(c, "logging")) 2736 eps, err := s.State.InferEndpoints("logging", "wordpress") 2737 c.Assert(err, jc.ErrorIsNil) 2738 rel, err := s.State.AddRelation(eps...) 2739 c.Assert(err, jc.ErrorIsNil) 2740 ru, err := rel.Unit(wordpress0) 2741 c.Assert(err, jc.ErrorIsNil) 2742 err = ru.EnterScope(nil) 2743 c.Assert(err, jc.ErrorIsNil) 2744 logging0, err := s.State.Unit("logging/0") 2745 c.Assert(err, jc.ErrorIsNil) 2746 2747 s.BlockRemoveObject(c, "TestBlockRemoveDestroySubordinateUnits") 2748 // Try to destroy the subordinate alone; check it fails. 2749 err = s.applicationAPI.DestroyUnits(params.DestroyApplicationUnits{ 2750 UnitNames: []string{"logging/0"}, 2751 }) 2752 s.AssertBlocked(c, err, "TestBlockRemoveDestroySubordinateUnits") 2753 assertLife(c, rel, state.Alive) 2754 assertLife(c, wordpress0, state.Alive) 2755 assertLife(c, logging0, state.Alive) 2756 2757 err = s.applicationAPI.DestroyUnits(params.DestroyApplicationUnits{ 2758 UnitNames: []string{"wordpress/0", "logging/0"}, 2759 }) 2760 s.AssertBlocked(c, err, "TestBlockRemoveDestroySubordinateUnits") 2761 assertLife(c, wordpress0, state.Alive) 2762 assertLife(c, logging0, state.Alive) 2763 assertLife(c, rel, state.Alive) 2764 } 2765 2766 func (s *applicationSuite) TestBlockChangesDestroySubordinateUnits(c *gc.C) { 2767 wordpress := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 2768 wordpress0, err := wordpress.AddUnit(state.AddUnitParams{}) 2769 c.Assert(err, jc.ErrorIsNil) 2770 s.AddTestingApplication(c, "logging", s.AddTestingCharm(c, "logging")) 2771 eps, err := s.State.InferEndpoints("logging", "wordpress") 2772 c.Assert(err, jc.ErrorIsNil) 2773 rel, err := s.State.AddRelation(eps...) 2774 c.Assert(err, jc.ErrorIsNil) 2775 ru, err := rel.Unit(wordpress0) 2776 c.Assert(err, jc.ErrorIsNil) 2777 err = ru.EnterScope(nil) 2778 c.Assert(err, jc.ErrorIsNil) 2779 logging0, err := s.State.Unit("logging/0") 2780 c.Assert(err, jc.ErrorIsNil) 2781 2782 s.BlockAllChanges(c, "TestBlockChangesDestroySubordinateUnits") 2783 // Try to destroy the subordinate alone; check it fails. 2784 err = s.applicationAPI.DestroyUnits(params.DestroyApplicationUnits{ 2785 UnitNames: []string{"logging/0"}, 2786 }) 2787 s.AssertBlocked(c, err, "TestBlockChangesDestroySubordinateUnits") 2788 assertLife(c, rel, state.Alive) 2789 assertLife(c, wordpress0, state.Alive) 2790 assertLife(c, logging0, state.Alive) 2791 2792 err = s.applicationAPI.DestroyUnits(params.DestroyApplicationUnits{ 2793 UnitNames: []string{"wordpress/0", "logging/0"}, 2794 }) 2795 s.AssertBlocked(c, err, "TestBlockChangesDestroySubordinateUnits") 2796 assertLife(c, wordpress0, state.Alive) 2797 assertLife(c, logging0, state.Alive) 2798 assertLife(c, rel, state.Alive) 2799 } 2800 2801 func (s *applicationSuite) TestBlockDestroyDestroySubordinateUnits(c *gc.C) { 2802 wordpress := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 2803 wordpress0, err := wordpress.AddUnit(state.AddUnitParams{}) 2804 c.Assert(err, jc.ErrorIsNil) 2805 s.AddTestingApplication(c, "logging", s.AddTestingCharm(c, "logging")) 2806 eps, err := s.State.InferEndpoints("logging", "wordpress") 2807 c.Assert(err, jc.ErrorIsNil) 2808 rel, err := s.State.AddRelation(eps...) 2809 c.Assert(err, jc.ErrorIsNil) 2810 ru, err := rel.Unit(wordpress0) 2811 c.Assert(err, jc.ErrorIsNil) 2812 err = ru.EnterScope(nil) 2813 c.Assert(err, jc.ErrorIsNil) 2814 logging0, err := s.State.Unit("logging/0") 2815 c.Assert(err, jc.ErrorIsNil) 2816 2817 s.BlockDestroyModel(c, "TestBlockDestroyDestroySubordinateUnits") 2818 // Try to destroy the subordinate alone; check it fails. 2819 err = s.applicationAPI.DestroyUnits(params.DestroyApplicationUnits{ 2820 UnitNames: []string{"logging/0"}, 2821 }) 2822 c.Assert(err, gc.ErrorMatches, `no units were destroyed: unit "logging/0" is a subordinate`) 2823 assertLife(c, logging0, state.Alive) 2824 2825 s.assertDestroySubordinateUnits(c, wordpress0, logging0) 2826 } 2827 2828 func (s *applicationSuite) TestClientSetApplicationConstraints(c *gc.C) { 2829 app := s.AddTestingApplication(c, "dummy", s.AddTestingCharm(c, "dummy")) 2830 2831 // Update constraints for the application. 2832 cons, err := constraints.Parse("mem=4096", "cores=2") 2833 c.Assert(err, jc.ErrorIsNil) 2834 err = s.applicationAPI.SetConstraints(params.SetConstraints{ApplicationName: "dummy", Constraints: cons}) 2835 c.Assert(err, jc.ErrorIsNil) 2836 2837 // Ensure the constraints have been correctly updated. 2838 obtained, err := app.Constraints() 2839 c.Assert(err, jc.ErrorIsNil) 2840 c.Assert(obtained, gc.DeepEquals, cons) 2841 } 2842 2843 func (s *applicationSuite) setupSetApplicationConstraints(c *gc.C) (*state.Application, constraints.Value) { 2844 app := s.AddTestingApplication(c, "dummy", s.AddTestingCharm(c, "dummy")) 2845 // Update constraints for the application. 2846 cons, err := constraints.Parse("mem=4096", "cores=2") 2847 c.Assert(err, jc.ErrorIsNil) 2848 return app, cons 2849 } 2850 2851 func (s *applicationSuite) assertSetApplicationConstraints(c *gc.C, application *state.Application, cons constraints.Value) { 2852 err := s.applicationAPI.SetConstraints(params.SetConstraints{ApplicationName: "dummy", Constraints: cons}) 2853 c.Assert(err, jc.ErrorIsNil) 2854 // Ensure the constraints have been correctly updated. 2855 obtained, err := application.Constraints() 2856 c.Assert(err, jc.ErrorIsNil) 2857 c.Assert(obtained, gc.DeepEquals, cons) 2858 } 2859 2860 func (s *applicationSuite) assertSetApplicationConstraintsBlocked(c *gc.C, msg string, application *state.Application, cons constraints.Value) { 2861 err := s.applicationAPI.SetConstraints(params.SetConstraints{ApplicationName: "dummy", Constraints: cons}) 2862 s.AssertBlocked(c, err, msg) 2863 } 2864 2865 func (s *applicationSuite) TestBlockDestroySetApplicationConstraints(c *gc.C) { 2866 app, cons := s.setupSetApplicationConstraints(c) 2867 s.BlockDestroyModel(c, "TestBlockDestroySetApplicationConstraints") 2868 s.assertSetApplicationConstraints(c, app, cons) 2869 } 2870 2871 func (s *applicationSuite) TestBlockRemoveSetApplicationConstraints(c *gc.C) { 2872 app, cons := s.setupSetApplicationConstraints(c) 2873 s.BlockRemoveObject(c, "TestBlockRemoveSetApplicationConstraints") 2874 s.assertSetApplicationConstraints(c, app, cons) 2875 } 2876 2877 func (s *applicationSuite) TestBlockChangesSetApplicationConstraints(c *gc.C) { 2878 app, cons := s.setupSetApplicationConstraints(c) 2879 s.BlockAllChanges(c, "TestBlockChangesSetApplicationConstraints") 2880 s.assertSetApplicationConstraintsBlocked(c, "TestBlockChangesSetApplicationConstraints", app, cons) 2881 } 2882 2883 func (s *applicationSuite) TestClientGetApplicationConstraints(c *gc.C) { 2884 fooConstraints := constraints.MustParse("mem=4G") 2885 s.Factory.MakeApplication(c, &factory.ApplicationParams{ 2886 Name: "foo", 2887 Constraints: fooConstraints, 2888 }) 2889 barConstraints := constraints.MustParse("mem=128G", "cores=64") 2890 s.Factory.MakeApplication(c, &factory.ApplicationParams{ 2891 Name: "bar", 2892 Constraints: barConstraints, 2893 }) 2894 2895 results, err := s.applicationAPI.GetConstraints(params.Entities{ 2896 Entities: []params.Entity{ 2897 {"wat"}, {"machine-0"}, {"user-foo"}, 2898 {"application-foo"}, {"application-bar"}, {"application-wat"}, 2899 }, 2900 }) 2901 c.Assert(err, jc.ErrorIsNil) 2902 c.Assert(results, jc.DeepEquals, params.ApplicationGetConstraintsResults{ 2903 Results: []params.ApplicationConstraint{ 2904 { 2905 Error: ¶ms.Error{Message: `"wat" is not a valid tag`}, 2906 }, { 2907 Error: ¶ms.Error{Message: `unexpected tag type, expected application, got machine`}, 2908 }, { 2909 Error: ¶ms.Error{Message: `unexpected tag type, expected application, got user`}, 2910 }, { 2911 Constraints: fooConstraints, 2912 }, { 2913 Constraints: barConstraints, 2914 }, { 2915 Error: ¶ms.Error{Message: `application "wat" not found`, Code: "not found"}, 2916 }, 2917 }}) 2918 } 2919 2920 func (s *applicationSuite) checkEndpoints(c *gc.C, mysqlAppName string, endpoints map[string]params.CharmRelation) { 2921 c.Assert(endpoints["wordpress"], gc.DeepEquals, params.CharmRelation{ 2922 Name: "db", 2923 Role: "requirer", 2924 Interface: "mysql", 2925 Optional: false, 2926 Limit: 1, 2927 Scope: "global", 2928 }) 2929 ep := params.CharmRelation{ 2930 Name: "server", 2931 Role: "provider", 2932 Interface: "mysql", 2933 Scope: "global", 2934 } 2935 // Remote applications don't use scope. 2936 if mysqlAppName == "hosted-mysql" { 2937 ep.Scope = "" 2938 } 2939 c.Assert(endpoints[mysqlAppName], gc.DeepEquals, ep) 2940 } 2941 2942 func (s *applicationSuite) setupRelationScenario(c *gc.C) { 2943 s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 2944 s.AddTestingApplication(c, "logging", s.AddTestingCharm(c, "logging")) 2945 eps, err := s.State.InferEndpoints("logging", "wordpress") 2946 c.Assert(err, jc.ErrorIsNil) 2947 _, err = s.State.AddRelation(eps...) 2948 c.Assert(err, jc.ErrorIsNil) 2949 } 2950 2951 func (s *applicationSuite) assertAddRelation(c *gc.C, endpoints, viaCIDRs []string) { 2952 s.setupRelationScenario(c) 2953 2954 res, err := s.applicationAPI.AddRelation(params.AddRelation{Endpoints: endpoints, ViaCIDRs: viaCIDRs}) 2955 c.Assert(err, jc.ErrorIsNil) 2956 // Show that the relation was added. 2957 wpApp, err := s.State.Application("wordpress") 2958 c.Assert(err, jc.ErrorIsNil) 2959 rels, err := wpApp.Relations() 2960 // There are 2 relations - the logging-wordpress one set up in the 2961 // scenario and the one created in this test. 2962 c.Assert(len(rels), gc.Equals, 2) 2963 2964 // We may be related to a local application or a remote offer 2965 // or an application in another model. 2966 var mySqlApplication state.ApplicationEntity 2967 mySqlApplication, err = s.State.RemoteApplication("hosted-mysql") 2968 if errors.IsNotFound(err) { 2969 mySqlApplication, err = s.State.RemoteApplication("othermysql") 2970 if errors.IsNotFound(err) { 2971 mySqlApplication, err = s.State.Application("mysql") 2972 c.Assert(err, jc.ErrorIsNil) 2973 s.checkEndpoints(c, "mysql", res.Endpoints) 2974 } else { 2975 c.Assert(err, jc.ErrorIsNil) 2976 s.checkEndpoints(c, "othermysql", res.Endpoints) 2977 } 2978 } else { 2979 c.Assert(err, jc.ErrorIsNil) 2980 s.checkEndpoints(c, "hosted-mysql", res.Endpoints) 2981 } 2982 c.Assert(err, jc.ErrorIsNil) 2983 rels, err = mySqlApplication.Relations() 2984 c.Assert(err, jc.ErrorIsNil) 2985 c.Assert(len(rels), gc.Equals, 1) 2986 } 2987 2988 func (s *applicationSuite) TestSuccessfullyAddRelation(c *gc.C) { 2989 endpoints := []string{"wordpress", "mysql"} 2990 s.assertAddRelation(c, endpoints, nil) 2991 } 2992 2993 func (s *applicationSuite) TestBlockDestroyAddRelation(c *gc.C) { 2994 s.BlockDestroyModel(c, "TestBlockDestroyAddRelation") 2995 s.assertAddRelation(c, []string{"wordpress", "mysql"}, nil) 2996 } 2997 func (s *applicationSuite) TestBlockRemoveAddRelation(c *gc.C) { 2998 s.BlockRemoveObject(c, "TestBlockRemoveAddRelation") 2999 s.assertAddRelation(c, []string{"wordpress", "mysql"}, nil) 3000 } 3001 3002 func (s *applicationSuite) TestBlockChangesAddRelation(c *gc.C) { 3003 s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 3004 s.BlockAllChanges(c, "TestBlockChangesAddRelation") 3005 _, err := s.applicationAPI.AddRelation(params.AddRelation{Endpoints: []string{"wordpress", "mysql"}}) 3006 s.AssertBlocked(c, err, "TestBlockChangesAddRelation") 3007 } 3008 3009 func (s *applicationSuite) TestSuccessfullyAddRelationSwapped(c *gc.C) { 3010 // Show that the order of the applications listed in the AddRelation call 3011 // does not matter. This is a repeat of the previous test with the application 3012 // names swapped. 3013 endpoints := []string{"mysql", "wordpress"} 3014 s.assertAddRelation(c, endpoints, nil) 3015 } 3016 3017 func (s *applicationSuite) TestCallWithOnlyOneEndpoint(c *gc.C) { 3018 s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 3019 endpoints := []string{"wordpress"} 3020 _, err := s.applicationAPI.AddRelation(params.AddRelation{Endpoints: endpoints}) 3021 c.Assert(err, gc.ErrorMatches, "no relations found") 3022 } 3023 3024 func (s *applicationSuite) TestCallWithOneEndpointTooMany(c *gc.C) { 3025 s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 3026 s.AddTestingApplication(c, "logging", s.AddTestingCharm(c, "logging")) 3027 endpoints := []string{"wordpress", "mysql", "logging"} 3028 _, err := s.applicationAPI.AddRelation(params.AddRelation{Endpoints: endpoints}) 3029 c.Assert(err, gc.ErrorMatches, "cannot relate 3 endpoints") 3030 } 3031 3032 func (s *applicationSuite) TestAddAlreadyAddedRelation(c *gc.C) { 3033 s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 3034 // Add a relation between wordpress and mysql. 3035 endpoints := []string{"wordpress", "mysql"} 3036 eps, err := s.State.InferEndpoints(endpoints...) 3037 c.Assert(err, jc.ErrorIsNil) 3038 _, err = s.State.AddRelation(eps...) 3039 c.Assert(err, jc.ErrorIsNil) 3040 // And try to add it again. 3041 _, err = s.applicationAPI.AddRelation(params.AddRelation{Endpoints: endpoints}) 3042 c.Assert(err, gc.ErrorMatches, `cannot add relation "wordpress:db mysql:server": relation wordpress:db mysql:server already exists`) 3043 } 3044 3045 func (s *applicationSuite) setupRemoteApplication(c *gc.C) { 3046 results, err := s.applicationAPI.Consume(params.ConsumeApplicationArgs{ 3047 Args: []params.ConsumeApplicationArg{ 3048 {ApplicationOfferDetails: params.ApplicationOfferDetails{ 3049 SourceModelTag: testing.ModelTag.String(), 3050 OfferName: "hosted-mysql", 3051 OfferUUID: "hosted-mysql-uuid", 3052 ApplicationDescription: "A pretty popular database", 3053 Endpoints: []params.RemoteEndpoint{ 3054 {Name: "server", Interface: "mysql", Role: "provider"}, 3055 }, 3056 }}, 3057 }, 3058 }) 3059 c.Assert(err, jc.ErrorIsNil) 3060 c.Assert(results.OneError(), gc.IsNil) 3061 } 3062 3063 func (s *applicationSuite) TestAddRemoteRelation(c *gc.C) { 3064 s.setupRemoteApplication(c) 3065 // There's already a wordpress in the scenario this assertion sets up. 3066 s.assertAddRelation(c, []string{"wordpress", "hosted-mysql"}, nil) 3067 } 3068 3069 func (s *applicationSuite) TestAddRemoteRelationWithRelName(c *gc.C) { 3070 s.setupRemoteApplication(c) 3071 s.assertAddRelation(c, []string{"wordpress", "hosted-mysql:server"}, nil) 3072 } 3073 3074 func (s *applicationSuite) TestAddRemoteRelationVia(c *gc.C) { 3075 s.setupRemoteApplication(c) 3076 s.assertAddRelation(c, []string{"wordpress", "hosted-mysql:server"}, []string{"192.168.0.0/16"}) 3077 3078 rel, err := s.State.KeyRelation("wordpress:db hosted-mysql:server") 3079 c.Assert(err, jc.ErrorIsNil) 3080 w := rel.WatchRelationEgressNetworks() 3081 defer statetesting.AssertStop(c, w) 3082 wc := statetesting.NewStringsWatcherC(c, s.State, w) 3083 wc.AssertChange("192.168.0.0/16") 3084 wc.AssertNoChange() 3085 } 3086 3087 func (s *applicationSuite) TestAddRemoteRelationOnlyOneEndpoint(c *gc.C) { 3088 s.setupRemoteApplication(c) 3089 endpoints := []string{"hosted-mysql"} 3090 _, err := s.applicationAPI.AddRelation(params.AddRelation{Endpoints: endpoints}) 3091 c.Assert(err, gc.ErrorMatches, "no relations found") 3092 } 3093 3094 func (s *applicationSuite) TestAlreadyAddedRemoteRelation(c *gc.C) { 3095 s.setupRemoteApplication(c) 3096 endpoints := []string{"wordpress", "hosted-mysql"} 3097 s.assertAddRelation(c, endpoints, nil) 3098 3099 // And try to add it again. 3100 _, err := s.applicationAPI.AddRelation(params.AddRelation{Endpoints: endpoints}) 3101 c.Assert(err, gc.ErrorMatches, regexp.QuoteMeta(`cannot add relation "wordpress:db hosted-mysql:server": relation wordpress:db hosted-mysql:server already exists`)) 3102 } 3103 3104 func (s *applicationSuite) TestRemoteRelationInvalidEndpoint(c *gc.C) { 3105 s.setupRemoteApplication(c) 3106 s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 3107 3108 endpoints := []string{"wordpress", "hosted-mysql:nope"} 3109 _, err := s.applicationAPI.AddRelation(params.AddRelation{Endpoints: endpoints}) 3110 c.Assert(err, gc.ErrorMatches, `remote application "hosted-mysql" has no "nope" relation`) 3111 } 3112 3113 func (s *applicationSuite) TestRemoteRelationNoMatchingEndpoint(c *gc.C) { 3114 results, err := s.applicationAPI.Consume(params.ConsumeApplicationArgs{ 3115 Args: []params.ConsumeApplicationArg{ 3116 {ApplicationOfferDetails: params.ApplicationOfferDetails{ 3117 SourceModelTag: testing.ModelTag.String(), 3118 OfferName: "hosted-db2", 3119 OfferUUID: "hosted-db2-uuid", 3120 Endpoints: []params.RemoteEndpoint{ 3121 {Name: "database", Interface: "db2", Role: "provider"}, 3122 }, 3123 }}, 3124 }, 3125 }) 3126 c.Assert(err, jc.ErrorIsNil) 3127 c.Assert(results.OneError(), gc.IsNil) 3128 3129 s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 3130 endpoints := []string{"wordpress", "hosted-db2"} 3131 _, err = s.applicationAPI.AddRelation(params.AddRelation{Endpoints: endpoints}) 3132 c.Assert(err, gc.ErrorMatches, "no relations found") 3133 } 3134 3135 func (s *applicationSuite) TestRemoteRelationApplicationNotFound(c *gc.C) { 3136 s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 3137 endpoints := []string{"wordpress", "unknown"} 3138 _, err := s.applicationAPI.AddRelation(params.AddRelation{Endpoints: endpoints}) 3139 c.Assert(err, gc.ErrorMatches, `application "unknown" not found`) 3140 }