github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/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 "regexp" 9 "time" 10 11 "github.com/juju/charm/v12" 12 "github.com/juju/errors" 13 "github.com/juju/names/v5" 14 jc "github.com/juju/testing/checkers" 15 gc "gopkg.in/check.v1" 16 17 unitassignerapi "github.com/juju/juju/api/agent/unitassigner" 18 "github.com/juju/juju/apiserver/common" 19 commontesting "github.com/juju/juju/apiserver/common/testing" 20 "github.com/juju/juju/apiserver/facades/client/application" 21 apiservertesting "github.com/juju/juju/apiserver/testing" 22 "github.com/juju/juju/core/arch" 23 "github.com/juju/juju/core/constraints" 24 "github.com/juju/juju/core/instance" 25 "github.com/juju/juju/core/model" 26 "github.com/juju/juju/core/network" 27 "github.com/juju/juju/core/network/firewall" 28 "github.com/juju/juju/core/status" 29 jujutesting "github.com/juju/juju/juju/testing" 30 "github.com/juju/juju/rpc/params" 31 "github.com/juju/juju/state" 32 "github.com/juju/juju/state/stateenvirons" 33 statetesting "github.com/juju/juju/state/testing" 34 "github.com/juju/juju/storage/poolmanager" 35 "github.com/juju/juju/testcharms" 36 "github.com/juju/juju/testing" 37 "github.com/juju/juju/testing/factory" 38 ) 39 40 type applicationSuite struct { 41 jujutesting.JujuConnSuite 42 commontesting.BlockHelper 43 44 applicationAPI *application.APIBase 45 application *state.Application 46 authorizer *apiservertesting.FakeAuthorizer 47 lastKnownRev map[string]int 48 } 49 50 var _ = gc.Suite(&applicationSuite{}) 51 52 func (s *applicationSuite) SetUpTest(c *gc.C) { 53 s.JujuConnSuite.SetUpTest(c) 54 s.BlockHelper = commontesting.NewBlockHelper(s.APIState) 55 s.AddCleanup(func(*gc.C) { s.BlockHelper.Close() }) 56 57 s.application = s.Factory.MakeApplication(c, nil) 58 59 s.authorizer = &apiservertesting.FakeAuthorizer{ 60 Tag: s.AdminUserTag(c), 61 } 62 s.applicationAPI = s.makeAPI(c) 63 s.lastKnownRev = make(map[string]int) 64 } 65 66 func (s *applicationSuite) makeAPI(c *gc.C) *application.APIBase { 67 resources := common.NewResources() 68 c.Assert(resources.RegisterNamed("dataDir", common.StringResource(c.MkDir())), jc.ErrorIsNil) 69 storageAccess, err := application.GetStorageState(s.State) 70 c.Assert(err, jc.ErrorIsNil) 71 model, err := s.State.Model() 72 c.Assert(err, jc.ErrorIsNil) 73 blockChecker := common.NewBlockChecker(s.State) 74 registry := stateenvirons.NewStorageProviderRegistry(s.Environ) 75 pm := poolmanager.New(state.NewStateSettings(s.State), registry) 76 api, err := application.NewAPIBase( 77 application.GetState(s.State), 78 storageAccess, 79 s.authorizer, 80 nil, 81 nil, 82 blockChecker, 83 application.GetModel(model), 84 nil, // leadership not used in these tests. 85 application.CharmToStateCharm, 86 application.DeployApplication, 87 pm, 88 registry, 89 common.NewResources(), 90 nil, // CAAS Broker not used in this suite. 91 ) 92 c.Assert(err, jc.ErrorIsNil) 93 return api 94 } 95 96 func (s *applicationSuite) setupApplicationDeploy(c *gc.C, args string) (string, charm.Charm, constraints.Value) { 97 curl, ch := s.addCharmToState(c, "ch:jammy/dummy-42", "dummy") 98 cons := constraints.MustParse(args) 99 return curl, ch, cons 100 } 101 102 func (s *applicationSuite) assertApplicationDeployPrincipal(c *gc.C, curl string, ch charm.Charm, mem4g constraints.Value) { 103 results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{ 104 Applications: []params.ApplicationDeploy{{ 105 CharmURL: curl, 106 CharmOrigin: createCharmOriginFromURL(curl), 107 ApplicationName: "application", 108 NumUnits: 3, 109 Constraints: mem4g, 110 }}}) 111 c.Assert(err, jc.ErrorIsNil) 112 c.Assert(results.Results, gc.HasLen, 1) 113 c.Assert(results.Results[0].Error, gc.IsNil) 114 apiservertesting.AssertPrincipalApplicationDeployed(c, s.State, "application", curl, false, ch, mem4g) 115 } 116 117 func (s *applicationSuite) assertApplicationDeployPrincipalBlocked(c *gc.C, msg string, curl string, mem4g constraints.Value) { 118 _, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{ 119 Applications: []params.ApplicationDeploy{{ 120 CharmURL: curl, 121 CharmOrigin: createCharmOriginFromURL(curl), 122 ApplicationName: "application", 123 NumUnits: 3, 124 Constraints: mem4g, 125 }}}) 126 s.AssertBlocked(c, err, msg) 127 } 128 129 func (s *applicationSuite) TestBlockDestroyApplicationDeployPrincipal(c *gc.C) { 130 curl, bundle, cons := s.setupApplicationDeploy(c, "arch=amd64 mem=4G") 131 s.BlockDestroyModel(c, "TestBlockDestroyApplicationDeployPrincipal") 132 s.assertApplicationDeployPrincipal(c, curl, bundle, cons) 133 } 134 135 func (s *applicationSuite) TestBlockRemoveApplicationDeployPrincipal(c *gc.C) { 136 curl, bundle, cons := s.setupApplicationDeploy(c, "arch=amd64 mem=4G") 137 s.BlockRemoveObject(c, "TestBlockRemoveApplicationDeployPrincipal") 138 s.assertApplicationDeployPrincipal(c, curl, bundle, cons) 139 } 140 141 func (s *applicationSuite) TestBlockChangesApplicationDeployPrincipal(c *gc.C) { 142 curl, _, cons := s.setupApplicationDeploy(c, "mem=4G") 143 s.BlockAllChanges(c, "TestBlockChangesApplicationDeployPrincipal") 144 s.assertApplicationDeployPrincipalBlocked(c, "TestBlockChangesApplicationDeployPrincipal", curl, cons) 145 } 146 147 func (s *applicationSuite) TestApplicationDeploySubordinate(c *gc.C) { 148 curl, ch := s.addCharmToState(c, "ch:utopic/logging-47", "logging") 149 results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{ 150 Applications: []params.ApplicationDeploy{{ 151 CharmURL: curl, 152 CharmOrigin: createCharmOriginFromURL(curl), 153 ApplicationName: "application-name", 154 }}}) 155 c.Assert(err, jc.ErrorIsNil) 156 c.Assert(results.Results, gc.HasLen, 1) 157 c.Assert(results.Results[0].Error, gc.IsNil) 158 159 app, err := s.State.Application("application-name") 160 c.Assert(err, jc.ErrorIsNil) 161 charm, force, err := app.Charm() 162 c.Assert(err, jc.ErrorIsNil) 163 c.Assert(force, jc.IsFalse) 164 c.Assert(charm.URL(), gc.DeepEquals, curl) 165 c.Assert(charm.Meta(), gc.DeepEquals, ch.Meta()) 166 c.Assert(charm.Config(), gc.DeepEquals, ch.Config()) 167 168 units, err := app.AllUnits() 169 c.Assert(err, jc.ErrorIsNil) 170 c.Assert(units, gc.HasLen, 0) 171 } 172 173 func (s *applicationSuite) combinedSettings(ch *state.Charm, inSettings charm.Settings) charm.Settings { 174 result := ch.Config().DefaultSettings() 175 for name, value := range inSettings { 176 result[name] = value 177 } 178 return result 179 } 180 181 func (s *applicationSuite) TestApplicationDeployConfig(c *gc.C) { 182 curl, _ := s.addCharmToState(c, "ch:jammy/dummy-0", "dummy") 183 results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{ 184 Applications: []params.ApplicationDeploy{{ 185 CharmURL: curl, 186 CharmOrigin: createCharmOriginFromURL(curl), 187 ApplicationName: "application-name", 188 NumUnits: 1, 189 ConfigYAML: "application-name:\n username: fred", 190 }}}) 191 c.Assert(err, jc.ErrorIsNil) 192 c.Assert(results.Results, gc.HasLen, 1) 193 c.Assert(results.Results[0].Error, gc.IsNil) 194 195 app, err := s.State.Application("application-name") 196 c.Assert(err, jc.ErrorIsNil) 197 settings, err := app.CharmConfig(model.GenerationMaster) 198 c.Assert(err, jc.ErrorIsNil) 199 ch, _, err := app.Charm() 200 c.Assert(err, jc.ErrorIsNil) 201 c.Assert(settings, gc.DeepEquals, s.combinedSettings(ch, charm.Settings{"username": "fred"})) 202 } 203 204 func (s *applicationSuite) TestApplicationDeployConfigError(c *gc.C) { 205 // TODO(fwereade): test Config/ConfigYAML handling directly on srvClient. 206 // Can't be done cleanly until it's extracted similarly to Machiner. 207 curl, _ := s.addCharmToState(c, "ch:jammy/dummy-0", "dummy") 208 results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{ 209 Applications: []params.ApplicationDeploy{{ 210 CharmURL: curl, 211 CharmOrigin: createCharmOriginFromURL(curl), 212 ApplicationName: "application-name", 213 NumUnits: 1, 214 ConfigYAML: "application-name:\n skill-level: fred", 215 }}}) 216 c.Assert(err, jc.ErrorIsNil) 217 c.Assert(results.Results, gc.HasLen, 1) 218 c.Assert(results.Results[0].Error, gc.ErrorMatches, `option "skill-level" expected int, got "fred"`) 219 _, err = s.State.Application("application-name") 220 c.Assert(err, jc.Satisfies, errors.IsNotFound) 221 } 222 223 func (s *applicationSuite) TestApplicationDeployToMachine(c *gc.C) { 224 curl, ch := s.addCharmToState(c, "ch:jammy/dummy-0", "dummy") 225 226 machine, err := s.State.AddMachine(state.UbuntuBase("22.04"), state.JobHostUnits) 227 c.Assert(err, jc.ErrorIsNil) 228 229 arch := arch.DefaultArchitecture 230 hwChar := &instance.HardwareCharacteristics{ 231 Arch: &arch, 232 } 233 instId := instance.Id("i-host-machine") 234 err = machine.SetProvisioned(instId, "", "fake-nonce", hwChar) 235 c.Assert(err, jc.ErrorIsNil) 236 237 results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{ 238 Applications: []params.ApplicationDeploy{{ 239 CharmURL: curl, 240 CharmOrigin: createCharmOriginFromURL(curl), 241 ApplicationName: "application-name", 242 NumUnits: 1, 243 ConfigYAML: "application-name:\n username: fred", 244 }}}) 245 c.Assert(err, jc.ErrorIsNil) 246 c.Assert(results.Results, gc.HasLen, 1) 247 c.Assert(results.Results[0].Error, gc.IsNil) 248 249 app, err := s.State.Application("application-name") 250 c.Assert(err, jc.ErrorIsNil) 251 charm, force, err := app.Charm() 252 c.Assert(err, jc.ErrorIsNil) 253 c.Assert(force, jc.IsFalse) 254 c.Assert(charm.URL(), gc.DeepEquals, curl) 255 c.Assert(charm.Meta(), gc.DeepEquals, ch.Meta()) 256 c.Assert(charm.Config(), gc.DeepEquals, ch.Config()) 257 258 errs, err := unitassignerapi.New(s.APIState).AssignUnits([]names.UnitTag{names.NewUnitTag("application-name/0")}) 259 c.Assert(errs, gc.DeepEquals, []error{nil}) 260 c.Assert(err, jc.ErrorIsNil) 261 262 units, err := app.AllUnits() 263 c.Assert(err, jc.ErrorIsNil) 264 c.Assert(units, gc.HasLen, 1) 265 266 mid, err := units[0].AssignedMachineId() 267 c.Assert(err, jc.ErrorIsNil) 268 c.Assert(mid, gc.Equals, machine.Id()) 269 } 270 271 func (s *applicationSuite) TestApplicationDeployToMachineWithLXDProfile(c *gc.C) { 272 curl, ch := s.addCharmToState(c, "ch:jammy/lxd-profile-0", "lxd-profile") 273 274 machine, err := s.State.AddMachine(state.UbuntuBase("22.04"), state.JobHostUnits) 275 c.Assert(err, jc.ErrorIsNil) 276 277 arch := arch.DefaultArchitecture 278 hwChar := &instance.HardwareCharacteristics{ 279 Arch: &arch, 280 } 281 instId := instance.Id("i-host-machine") 282 err = machine.SetProvisioned(instId, "", "fake-nonce", hwChar) 283 c.Assert(err, jc.ErrorIsNil) 284 285 results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{ 286 Applications: []params.ApplicationDeploy{{ 287 CharmURL: curl, 288 CharmOrigin: createCharmOriginFromURL(curl), 289 ApplicationName: "application-name", 290 NumUnits: 1, 291 }}}) 292 c.Assert(err, jc.ErrorIsNil) 293 c.Assert(results.Results, gc.HasLen, 1) 294 c.Assert(results.Results[0].Error, gc.IsNil) 295 296 application, err := s.State.Application("application-name") 297 c.Assert(err, jc.ErrorIsNil) 298 expected, force, err := application.Charm() 299 c.Assert(err, jc.ErrorIsNil) 300 c.Assert(force, jc.IsFalse) 301 c.Assert(expected.URL(), gc.DeepEquals, curl) 302 c.Assert(expected.Meta(), gc.DeepEquals, ch.Meta()) 303 c.Assert(expected.Config(), gc.DeepEquals, ch.Config()) 304 305 expectedProfile := ch.(charm.LXDProfiler).LXDProfile() 306 c.Assert(expected.LXDProfile(), gc.DeepEquals, &state.LXDProfile{ 307 Description: expectedProfile.Description, 308 Config: expectedProfile.Config, 309 Devices: expectedProfile.Devices, 310 }) 311 312 errs, err := unitassignerapi.New(s.APIState).AssignUnits([]names.UnitTag{names.NewUnitTag("application-name/0")}) 313 c.Assert(errs, gc.DeepEquals, []error{nil}) 314 c.Assert(err, jc.ErrorIsNil) 315 316 units, err := application.AllUnits() 317 c.Assert(err, jc.ErrorIsNil) 318 c.Assert(units, gc.HasLen, 1) 319 320 mid, err := units[0].AssignedMachineId() 321 c.Assert(err, jc.ErrorIsNil) 322 c.Assert(mid, gc.Equals, machine.Id()) 323 } 324 325 func (s *applicationSuite) TestApplicationDeployToMachineWithInvalidLXDProfileAndForceStillSucceeds(c *gc.C) { 326 curl, ch := s.addCharmToState(c, "ch:jammy/lxd-profile-fail-0", "lxd-profile-fail") 327 328 machine, err := s.State.AddMachine(state.UbuntuBase("22.04"), state.JobHostUnits) 329 c.Assert(err, jc.ErrorIsNil) 330 331 arch := arch.DefaultArchitecture 332 hwChar := &instance.HardwareCharacteristics{ 333 Arch: &arch, 334 } 335 instId := instance.Id("i-host-machine") 336 err = machine.SetProvisioned(instId, "", "fake-nonce", hwChar) 337 c.Assert(err, jc.ErrorIsNil) 338 339 results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{ 340 Applications: []params.ApplicationDeploy{{ 341 CharmURL: curl, 342 CharmOrigin: createCharmOriginFromURL(curl), 343 ApplicationName: "application-name", 344 NumUnits: 1, 345 }}}) 346 c.Assert(err, jc.ErrorIsNil) 347 c.Assert(results.Results, gc.HasLen, 1) 348 c.Assert(results.Results[0].Error, gc.IsNil) 349 350 app, err := s.State.Application("application-name") 351 c.Assert(err, jc.ErrorIsNil) 352 expected, force, err := app.Charm() 353 c.Assert(err, jc.ErrorIsNil) 354 c.Assert(force, jc.IsFalse) 355 c.Assert(expected.URL(), gc.DeepEquals, curl) 356 c.Assert(expected.Meta(), gc.DeepEquals, ch.Meta()) 357 c.Assert(expected.Config(), gc.DeepEquals, ch.Config()) 358 359 expectedProfile := ch.(charm.LXDProfiler).LXDProfile() 360 c.Assert(expected.LXDProfile(), gc.DeepEquals, &state.LXDProfile{ 361 Description: expectedProfile.Description, 362 Config: expectedProfile.Config, 363 Devices: expectedProfile.Devices, 364 }) 365 366 errs, err := unitassignerapi.New(s.APIState).AssignUnits([]names.UnitTag{names.NewUnitTag("application-name/0")}) 367 c.Assert(errs, gc.DeepEquals, []error{nil}) 368 c.Assert(err, jc.ErrorIsNil) 369 370 units, err := app.AllUnits() 371 c.Assert(err, jc.ErrorIsNil) 372 c.Assert(units, gc.HasLen, 1) 373 374 mid, err := units[0].AssignedMachineId() 375 c.Assert(err, jc.ErrorIsNil) 376 c.Assert(mid, gc.Equals, machine.Id()) 377 } 378 379 func (s *applicationSuite) TestApplicationDeployToMachineNotFound(c *gc.C) { 380 results, err := s.applicationAPI.Deploy(params.ApplicationsDeploy{ 381 Applications: []params.ApplicationDeploy{{ 382 CharmURL: "ch:jammy/application-name-1", 383 CharmOrigin: ¶ms.CharmOrigin{Source: "charm-hub", Base: params.Base{Name: "ubuntu", Channel: "22.04/stable"}}, 384 ApplicationName: "application-name", 385 NumUnits: 1, 386 Placement: []*instance.Placement{instance.MustParsePlacement("42")}, 387 }}}) 388 c.Assert(err, jc.ErrorIsNil) 389 c.Assert(results.Results, gc.HasLen, 1) 390 c.Assert(results.Results[0].Error, gc.ErrorMatches, `cannot deploy "application-name" to machine 42: machine 42 not found`) 391 392 _, err = s.State.Application("application-name") 393 c.Assert(err, gc.ErrorMatches, `application "application-name" not found`) 394 } 395 396 func (s *applicationSuite) TestApplicationUpdateDoesNotSetMinUnitsWithLXDProfile(c *gc.C) { 397 series := "quantal" 398 repo := testcharms.RepoForSeries(series) 399 ch := repo.CharmDir("lxd-profile-fail") 400 ident := fmt.Sprintf("%s-%d", ch.Meta().Name, ch.Revision()) 401 curl := charm.MustParseURL(fmt.Sprintf("local:%s/%s", series, ident)) 402 _, err := jujutesting.PutCharm(s.State, curl, ch) 403 c.Assert(err, gc.ErrorMatches, `invalid lxd-profile.yaml: contains device type "unix-disk"`) 404 } 405 406 var clientAddApplicationUnitsTests = []struct { 407 about string 408 application string // if not set, defaults to 'dummy' 409 numUnits int 410 expected []string 411 to string 412 err string 413 }{ 414 { 415 about: "returns unit names", 416 numUnits: 3, 417 expected: []string{"dummy/0", "dummy/1", "dummy/2"}, 418 }, 419 { 420 about: "fails trying to add zero units", 421 err: "must add at least one unit", 422 }, 423 { 424 // Note: chained-state, we add 1 unit here, but the 3 units 425 // from the first condition still exist 426 about: "force the unit onto bootstrap machine", 427 numUnits: 1, 428 expected: []string{"dummy/3"}, 429 to: "0", 430 }, 431 { 432 about: "unknown application name", 433 application: "unknown-application", 434 numUnits: 1, 435 err: `application "unknown-application" not found`, 436 }, 437 } 438 439 func (s *applicationSuite) TestClientAddApplicationUnits(c *gc.C) { 440 s.AddTestingApplication(c, "dummy", s.AddTestingCharm(c, "dummy")) 441 for i, t := range clientAddApplicationUnitsTests { 442 c.Logf("test %d. %s", i, t.about) 443 applicationName := t.application 444 if applicationName == "" { 445 applicationName = "dummy" 446 } 447 args := params.AddApplicationUnits{ 448 ApplicationName: applicationName, 449 NumUnits: t.numUnits, 450 } 451 if t.to != "" { 452 args.Placement = []*instance.Placement{instance.MustParsePlacement(t.to)} 453 } 454 result, err := s.applicationAPI.AddUnits(args) 455 if t.err != "" { 456 c.Assert(err, gc.ErrorMatches, t.err) 457 continue 458 } 459 c.Assert(err, jc.ErrorIsNil) 460 c.Assert(result.Units, gc.DeepEquals, t.expected) 461 } 462 // Test that we actually assigned the unit to machine 0 463 forcedUnit, err := s.BackingState.Unit("dummy/3") 464 c.Assert(err, jc.ErrorIsNil) 465 assignedMachine, err := forcedUnit.AssignedMachineId() 466 c.Assert(err, jc.ErrorIsNil) 467 c.Assert(assignedMachine, gc.Equals, "0") 468 } 469 470 func (s *applicationSuite) TestAddApplicationUnitsToNewContainer(c *gc.C) { 471 app := s.AddTestingApplication(c, "dummy", s.AddTestingCharm(c, "dummy")) 472 machine, err := s.State.AddMachine(state.UbuntuBase("22.04"), state.JobHostUnits) 473 c.Assert(err, jc.ErrorIsNil) 474 475 _, err = s.applicationAPI.AddUnits(params.AddApplicationUnits{ 476 ApplicationName: "dummy", 477 NumUnits: 1, 478 Placement: []*instance.Placement{instance.MustParsePlacement("lxd:" + machine.Id())}, 479 }) 480 c.Assert(err, jc.ErrorIsNil) 481 482 units, err := app.AllUnits() 483 c.Assert(err, jc.ErrorIsNil) 484 mid, err := units[0].AssignedMachineId() 485 c.Assert(err, jc.ErrorIsNil) 486 c.Assert(mid, gc.Equals, machine.Id()+"/lxd/0") 487 } 488 489 var addApplicationUnitTests = []struct { 490 about string 491 application string // if not set, defaults to 'dummy' 492 expected []string 493 machineIds []string 494 placement []*instance.Placement 495 err string 496 }{ 497 { 498 about: "valid placement directives", 499 expected: []string{"dummy/0"}, 500 placement: []*instance.Placement{{Scope: "deadbeef-0bad-400d-8000-4b1d0d06f00d", Directive: "valid"}}, 501 machineIds: []string{"1"}, 502 }, { 503 about: "direct machine assignment placement directive", 504 expected: []string{"dummy/1", "dummy/2"}, 505 placement: []*instance.Placement{{Scope: "#", Directive: "1"}, {Scope: "lxd", Directive: "1"}}, 506 machineIds: []string{"1", "1/lxd/0"}, 507 }, { 508 about: "invalid placement directive", 509 err: ".* invalid placement is invalid", 510 expected: []string{"dummy/3"}, 511 placement: []*instance.Placement{{Scope: "deadbeef-0bad-400d-8000-4b1d0d06f00d", Directive: "invalid"}}, 512 }, 513 } 514 515 func (s *applicationSuite) TestAddApplicationUnits(c *gc.C) { 516 s.AddTestingApplication(c, "dummy", s.AddTestingCharm(c, "dummy")) 517 // Add a machine for the units to be placed on. 518 _, err := s.State.AddMachine(state.UbuntuBase("22.04"), state.JobHostUnits) 519 c.Assert(err, jc.ErrorIsNil) 520 for i, t := range addApplicationUnitTests { 521 c.Logf("test %d. %s", i, t.about) 522 applicationName := t.application 523 if applicationName == "" { 524 applicationName = "dummy" 525 } 526 result, err := s.applicationAPI.AddUnits(params.AddApplicationUnits{ 527 ApplicationName: applicationName, 528 NumUnits: len(t.expected), 529 Placement: t.placement, 530 }) 531 if t.err != "" { 532 c.Assert(err, gc.ErrorMatches, t.err) 533 continue 534 } 535 c.Assert(err, jc.ErrorIsNil) 536 c.Assert(result.Units, gc.DeepEquals, t.expected) 537 for i, unitName := range result.Units { 538 u, err := s.BackingState.Unit(unitName) 539 c.Assert(err, jc.ErrorIsNil) 540 assignedMachine, err := u.AssignedMachineId() 541 c.Assert(err, jc.ErrorIsNil) 542 c.Assert(assignedMachine, gc.Equals, t.machineIds[i]) 543 } 544 } 545 } 546 547 func (s *applicationSuite) assertAddApplicationUnits(c *gc.C) { 548 result, err := s.applicationAPI.AddUnits(params.AddApplicationUnits{ 549 ApplicationName: "dummy", 550 NumUnits: 3, 551 }) 552 c.Assert(err, jc.ErrorIsNil) 553 c.Assert(result.Units, gc.DeepEquals, []string{"dummy/0", "dummy/1", "dummy/2"}) 554 555 // Test that we actually assigned the unit to machine 0 556 forcedUnit, err := s.BackingState.Unit("dummy/0") 557 c.Assert(err, jc.ErrorIsNil) 558 assignedMachine, err := forcedUnit.AssignedMachineId() 559 c.Assert(err, jc.ErrorIsNil) 560 c.Assert(assignedMachine, gc.Equals, "0") 561 } 562 563 func (s *applicationSuite) TestApplicationCharmRelations(c *gc.C) { 564 s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 565 s.AddTestingApplication(c, "logging", s.AddTestingCharm(c, "logging")) 566 eps, err := s.State.InferEndpoints("logging", "wordpress") 567 c.Assert(err, jc.ErrorIsNil) 568 _, err = s.State.AddRelation(eps...) 569 c.Assert(err, jc.ErrorIsNil) 570 571 _, err = s.applicationAPI.CharmRelations(params.ApplicationCharmRelations{ApplicationName: "blah"}) 572 c.Assert(err, gc.ErrorMatches, `application "blah" not found`) 573 574 result, err := s.applicationAPI.CharmRelations(params.ApplicationCharmRelations{ApplicationName: "wordpress"}) 575 c.Assert(err, jc.ErrorIsNil) 576 c.Assert(result.CharmRelations, gc.DeepEquals, []string{ 577 "cache", "db", "juju-info", "logging-dir", "monitoring-port", "url", 578 }) 579 } 580 581 func (s *applicationSuite) assertAddApplicationUnitsBlocked(c *gc.C, msg string) { 582 _, err := s.applicationAPI.AddUnits(params.AddApplicationUnits{ 583 ApplicationName: "dummy", 584 NumUnits: 3, 585 }) 586 s.AssertBlocked(c, err, msg) 587 } 588 589 func (s *applicationSuite) TestBlockDestroyAddApplicationUnits(c *gc.C) { 590 s.AddTestingApplication(c, "dummy", s.AddTestingCharm(c, "dummy")) 591 s.BlockDestroyModel(c, "TestBlockDestroyAddApplicationUnits") 592 s.assertAddApplicationUnits(c) 593 } 594 595 func (s *applicationSuite) TestBlockRemoveAddApplicationUnits(c *gc.C) { 596 s.AddTestingApplication(c, "dummy", s.AddTestingCharm(c, "dummy")) 597 s.BlockRemoveObject(c, "TestBlockRemoveAddApplicationUnits") 598 s.assertAddApplicationUnits(c) 599 } 600 601 func (s *applicationSuite) TestBlockChangeAddApplicationUnits(c *gc.C) { 602 s.AddTestingApplication(c, "dummy", s.AddTestingCharm(c, "dummy")) 603 s.BlockAllChanges(c, "TestBlockChangeAddApplicationUnits") 604 s.assertAddApplicationUnitsBlocked(c, "TestBlockChangeAddApplicationUnits") 605 } 606 607 func (s *applicationSuite) TestAddUnitToMachineNotFound(c *gc.C) { 608 s.AddTestingApplication(c, "dummy", s.AddTestingCharm(c, "dummy")) 609 _, err := s.applicationAPI.AddUnits(params.AddApplicationUnits{ 610 ApplicationName: "dummy", 611 NumUnits: 3, 612 Placement: []*instance.Placement{instance.MustParsePlacement("42")}, 613 }) 614 c.Assert(err, gc.ErrorMatches, `acquiring machine to host unit "dummy/0": machine 42 not found`) 615 } 616 617 func (s *applicationSuite) TestApplicationExpose(c *gc.C) { 618 charm := s.AddTestingCharm(c, "dummy") 619 applicationNames := []string{"dummy-application", "exposed-application"} 620 apps := make([]*state.Application, len(applicationNames)) 621 var err error 622 for i, name := range applicationNames { 623 apps[i] = s.AddTestingApplication(c, name, charm) 624 c.Assert(apps[i].IsExposed(), jc.IsFalse) 625 } 626 err = apps[1].MergeExposeSettings(nil) 627 c.Assert(err, jc.ErrorIsNil) 628 c.Assert(apps[1].IsExposed(), jc.IsTrue) 629 630 s.assertApplicationExpose(c) 631 } 632 633 func (s *applicationSuite) TestApplicationExposeEndpoints(c *gc.C) { 634 charm := s.AddTestingCharm(c, "wordpress") 635 app := s.AddTestingApplication(c, "wordpress", charm) 636 c.Assert(app.IsExposed(), jc.IsFalse) 637 638 err := s.applicationAPI.Expose(params.ApplicationExpose{ 639 ApplicationName: app.Name(), 640 ExposedEndpoints: map[string]params.ExposedEndpoint{ 641 // Exposing an endpoint with no expose options implies 642 // expose to 0.0.0.0/0 and ::/0. 643 "monitoring-port": {}, 644 }, 645 }) 646 c.Assert(err, jc.ErrorIsNil) 647 648 got, err := s.State.Application(app.Name()) 649 c.Assert(err, jc.ErrorIsNil) 650 c.Assert(got.IsExposed(), gc.Equals, true) 651 c.Assert(got.ExposedEndpoints(), gc.DeepEquals, map[string]state.ExposedEndpoint{ 652 "monitoring-port": { 653 ExposeToCIDRs: []string{firewall.AllNetworksIPV4CIDR, firewall.AllNetworksIPV6CIDR}, 654 }, 655 }) 656 } 657 658 func (s *applicationSuite) TestApplicationExposeEndpointsWithPre29Client(c *gc.C) { 659 charm := s.AddTestingCharm(c, "wordpress") 660 app := s.AddTestingApplication(c, "wordpress", charm) 661 c.Assert(app.IsExposed(), jc.IsFalse) 662 663 err := s.applicationAPI.Expose(params.ApplicationExpose{ 664 ApplicationName: app.Name(), 665 // If no endpoint-specific expose params are provided, the call 666 // will emulate the behavior of a pre 2.9 controller where all 667 // ports are exposed to 0.0.0.0/0 and ::/0. 668 }) 669 c.Assert(err, jc.ErrorIsNil) 670 671 got, err := s.State.Application(app.Name()) 672 c.Assert(err, jc.ErrorIsNil) 673 c.Assert(got.IsExposed(), gc.Equals, true) 674 c.Assert(got.ExposedEndpoints(), gc.DeepEquals, map[string]state.ExposedEndpoint{ 675 "": { 676 ExposeToCIDRs: []string{firewall.AllNetworksIPV4CIDR, firewall.AllNetworksIPV6CIDR}, 677 }, 678 }) 679 } 680 681 func (s *applicationSuite) setupApplicationExpose(c *gc.C) { 682 charm := s.AddTestingCharm(c, "dummy") 683 applicationNames := []string{"dummy-application", "exposed-application"} 684 apps := make([]*state.Application, len(applicationNames)) 685 var err error 686 for i, name := range applicationNames { 687 apps[i] = s.AddTestingApplication(c, name, charm) 688 c.Assert(apps[i].IsExposed(), jc.IsFalse) 689 } 690 err = apps[1].MergeExposeSettings(nil) 691 c.Assert(err, jc.ErrorIsNil) 692 c.Assert(apps[1].IsExposed(), jc.IsTrue) 693 } 694 695 var applicationExposeTests = []struct { 696 about string 697 application string 698 exposedEndpointParams map[string]params.ExposedEndpoint 699 // 700 expExposed bool 701 expExposedEndpoints map[string]state.ExposedEndpoint 702 expErr string 703 }{ 704 { 705 about: "unknown application name", 706 application: "unknown-application", 707 expErr: `application "unknown-application" not found`, 708 }, 709 { 710 about: "expose all endpoints of an application ", 711 application: "dummy-application", 712 expExposed: true, 713 expExposedEndpoints: map[string]state.ExposedEndpoint{ 714 "": { 715 ExposeToCIDRs: []string{"0.0.0.0/0", "::/0"}, 716 }, 717 }, 718 }, 719 { 720 about: "expose an already exposed application", 721 application: "exposed-application", 722 expExposed: true, 723 expExposedEndpoints: map[string]state.ExposedEndpoint{ 724 "": { 725 ExposeToCIDRs: []string{"0.0.0.0/0", "::/0"}, 726 }, 727 }, 728 }, 729 { 730 about: "unknown endpoint name in expose parameters", 731 application: "dummy-application", 732 exposedEndpointParams: map[string]params.ExposedEndpoint{ 733 "bogus": {}, 734 }, 735 expErr: `endpoint "bogus" not found`, 736 }, 737 { 738 about: "unknown space name in expose parameters", 739 application: "dummy-application", 740 exposedEndpointParams: map[string]params.ExposedEndpoint{ 741 "": { 742 ExposeToSpaces: []string{"invaders"}, 743 }, 744 }, 745 expErr: `space "invaders" not found`, 746 }, 747 { 748 about: "expose an application and provide expose parameters", 749 application: "exposed-application", 750 exposedEndpointParams: map[string]params.ExposedEndpoint{ 751 "": { 752 ExposeToSpaces: []string{network.AlphaSpaceName}, 753 ExposeToCIDRs: []string{"13.37.0.0/16"}, 754 }, 755 }, 756 expExposed: true, 757 expExposedEndpoints: map[string]state.ExposedEndpoint{ 758 "": { 759 ExposeToSpaceIDs: []string{network.AlphaSpaceId}, 760 ExposeToCIDRs: []string{"13.37.0.0/16"}, 761 }, 762 }, 763 }, 764 } 765 766 func (s *applicationSuite) assertApplicationExpose(c *gc.C) { 767 for i, t := range applicationExposeTests { 768 c.Logf("test %d. %s", i, t.about) 769 err := s.applicationAPI.Expose(params.ApplicationExpose{ 770 ApplicationName: t.application, 771 ExposedEndpoints: t.exposedEndpointParams, 772 }) 773 if t.expErr != "" { 774 c.Assert(err, gc.ErrorMatches, t.expErr) 775 } else { 776 c.Assert(err, jc.ErrorIsNil) 777 app, err := s.State.Application(t.application) 778 c.Assert(err, jc.ErrorIsNil) 779 c.Assert(app.IsExposed(), gc.Equals, t.expExposed) 780 c.Assert(app.ExposedEndpoints(), gc.DeepEquals, t.expExposedEndpoints) 781 } 782 } 783 } 784 785 func (s *applicationSuite) assertApplicationExposeBlocked(c *gc.C, msg string) { 786 for i, t := range applicationExposeTests { 787 c.Logf("test %d. %s", i, t.about) 788 err := s.applicationAPI.Expose(params.ApplicationExpose{ 789 ApplicationName: t.application, 790 ExposedEndpoints: t.exposedEndpointParams, 791 }) 792 s.AssertBlocked(c, err, msg) 793 } 794 } 795 796 func (s *applicationSuite) TestBlockDestroyApplicationExpose(c *gc.C) { 797 s.setupApplicationExpose(c) 798 s.BlockDestroyModel(c, "TestBlockDestroyApplicationExpose") 799 s.assertApplicationExpose(c) 800 } 801 802 func (s *applicationSuite) TestBlockRemoveApplicationExpose(c *gc.C) { 803 s.setupApplicationExpose(c) 804 s.BlockRemoveObject(c, "TestBlockRemoveApplicationExpose") 805 s.assertApplicationExpose(c) 806 } 807 808 func (s *applicationSuite) TestBlockChangesApplicationExpose(c *gc.C) { 809 s.setupApplicationExpose(c) 810 s.BlockAllChanges(c, "TestBlockChangesApplicationExpose") 811 s.assertApplicationExposeBlocked(c, "TestBlockChangesApplicationExpose") 812 } 813 814 var applicationUnexposeTests = []struct { 815 about string 816 application string 817 err string 818 initial map[string]state.ExposedEndpoint 819 unexposeEndpoints []string 820 expExposed bool 821 expExposedEndpoints map[string]state.ExposedEndpoint 822 }{ 823 { 824 about: "unknown application name", 825 application: "unknown-application", 826 err: `application "unknown-application" not found`, 827 }, 828 { 829 about: "unexpose a application without specifying any endpoints", 830 application: "dummy-application", 831 initial: map[string]state.ExposedEndpoint{ 832 "": {}, 833 }, 834 expExposed: false, 835 }, 836 { 837 about: "unexpose specific application endpoint", 838 application: "dummy-application", 839 initial: map[string]state.ExposedEndpoint{ 840 "server": {}, 841 "server-admin": {}, 842 }, 843 unexposeEndpoints: []string{"server"}, 844 // The server-admin (and hence the app) should remain exposed 845 expExposed: true, 846 expExposedEndpoints: map[string]state.ExposedEndpoint{ 847 "server-admin": {ExposeToCIDRs: []string{"0.0.0.0/0", "::/0"}}, 848 }, 849 }, 850 { 851 about: "unexpose all currently exposed application endpoints", 852 application: "dummy-application", 853 initial: map[string]state.ExposedEndpoint{ 854 "server": {}, 855 "server-admin": {}, 856 }, 857 unexposeEndpoints: []string{"server", "server-admin"}, 858 // Application should now be unexposed as all its endpoints have 859 // been unexposed. 860 expExposed: false, 861 }, 862 { 863 about: "unexpose an already unexposed application", 864 application: "dummy-application", 865 initial: nil, 866 expExposed: false, 867 }, 868 } 869 870 func (s *applicationSuite) TestApplicationUnexpose(c *gc.C) { 871 charm := s.AddTestingCharm(c, "mysql") 872 for i, t := range applicationUnexposeTests { 873 c.Logf("test %d. %s", i, t.about) 874 app := s.AddTestingApplication(c, "dummy-application", charm) 875 if len(t.initial) != 0 { 876 err := app.MergeExposeSettings(t.initial) 877 c.Assert(err, jc.ErrorIsNil) 878 } 879 c.Assert(app.IsExposed(), gc.Equals, len(t.initial) != 0) 880 err := s.applicationAPI.Unexpose(params.ApplicationUnexpose{ 881 ApplicationName: t.application, 882 ExposedEndpoints: t.unexposeEndpoints, 883 }) 884 if t.err == "" { 885 c.Assert(err, jc.ErrorIsNil) 886 app.Refresh() 887 c.Assert(app.IsExposed(), gc.Equals, t.expExposed) 888 c.Assert(app.ExposedEndpoints(), gc.DeepEquals, t.expExposedEndpoints) 889 } else { 890 c.Assert(err, gc.ErrorMatches, t.err) 891 } 892 err = app.Destroy() 893 c.Assert(err, jc.ErrorIsNil) 894 } 895 } 896 897 func (s *applicationSuite) setupApplicationUnexpose(c *gc.C) *state.Application { 898 charm := s.AddTestingCharm(c, "dummy") 899 app := s.AddTestingApplication(c, "dummy-application", charm) 900 app.MergeExposeSettings(nil) 901 c.Assert(app.IsExposed(), gc.Equals, true) 902 return app 903 } 904 905 func (s *applicationSuite) assertApplicationUnexpose(c *gc.C, app *state.Application) { 906 err := s.applicationAPI.Unexpose(params.ApplicationUnexpose{ApplicationName: "dummy-application"}) 907 c.Assert(err, jc.ErrorIsNil) 908 app.Refresh() 909 c.Assert(app.IsExposed(), gc.Equals, false) 910 err = app.Destroy() 911 c.Assert(err, jc.ErrorIsNil) 912 } 913 914 func (s *applicationSuite) assertApplicationUnexposeBlocked(c *gc.C, app *state.Application, msg string) { 915 err := s.applicationAPI.Unexpose(params.ApplicationUnexpose{ApplicationName: "dummy-application"}) 916 s.AssertBlocked(c, err, msg) 917 err = app.Destroy() 918 c.Assert(err, jc.ErrorIsNil) 919 } 920 921 func (s *applicationSuite) TestBlockDestroyApplicationUnexpose(c *gc.C) { 922 app := s.setupApplicationUnexpose(c) 923 s.BlockDestroyModel(c, "TestBlockDestroyApplicationUnexpose") 924 s.assertApplicationUnexpose(c, app) 925 } 926 927 func (s *applicationSuite) TestBlockRemoveApplicationUnexpose(c *gc.C) { 928 app := s.setupApplicationUnexpose(c) 929 s.BlockRemoveObject(c, "TestBlockRemoveApplicationUnexpose") 930 s.assertApplicationUnexpose(c, app) 931 } 932 933 func (s *applicationSuite) TestBlockChangesApplicationUnexpose(c *gc.C) { 934 app := s.setupApplicationUnexpose(c) 935 s.BlockAllChanges(c, "TestBlockChangesApplicationUnexpose") 936 s.assertApplicationUnexposeBlocked(c, app, "TestBlockChangesApplicationUnexpose") 937 } 938 939 var applicationDestroyTests = []struct { 940 about string 941 application string 942 err string 943 }{ 944 { 945 about: "unknown application name", 946 application: "unknown-application", 947 err: `application "unknown-application" not found`, 948 }, 949 { 950 about: "destroy an application", 951 application: "dummy-application", 952 }, 953 { 954 about: "destroy an already destroyed application", 955 application: "dummy-application", 956 err: `application "dummy-application" not found`, 957 }, 958 } 959 960 func (s *applicationSuite) apiv16() *application.APIv16 { 961 return &application.APIv16{ 962 APIv17: &application.APIv17{ 963 APIv18: &application.APIv18{ 964 APIv19: &application.APIv19{ 965 APIv20: &application.APIv20{ 966 APIBase: s.applicationAPI, 967 }, 968 }, 969 }, 970 }, 971 } 972 } 973 974 func (s *applicationSuite) TestApplicationDestroy(c *gc.C) { 975 apiv16 := s.apiv16() 976 s.AddTestingApplication(c, "dummy-application", s.AddTestingCharm(c, "dummy")) 977 _, err := s.State.AddRemoteApplication(state.AddRemoteApplicationParams{ 978 Name: "remote-application", 979 SourceModel: s.Model.ModelTag(), 980 Token: "t0", 981 }) 982 c.Assert(err, jc.ErrorIsNil) 983 984 for i, t := range applicationDestroyTests { 985 c.Logf("test %d. %s", i, t.about) 986 err := apiv16.Destroy(params.ApplicationDestroy{ApplicationName: t.application}) 987 if t.err != "" { 988 c.Assert(err, gc.ErrorMatches, t.err) 989 } else { 990 c.Assert(err, jc.ErrorIsNil) 991 } 992 } 993 994 // Now do Destroy on an application with units. Destroy will 995 // cause the application to be not-Alive, but will not remove its 996 // document. 997 s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 998 applicationName := "wordpress" 999 app, err := s.State.Application(applicationName) 1000 c.Assert(err, jc.ErrorIsNil) 1001 err = apiv16.Destroy(params.ApplicationDestroy{ApplicationName: applicationName}) 1002 c.Assert(err, jc.ErrorIsNil) 1003 err = app.Refresh() 1004 c.Assert(err, jc.Satisfies, errors.IsNotFound) 1005 } 1006 1007 func assertLife(c *gc.C, entity state.Living, life state.Life) { 1008 err := entity.Refresh() 1009 c.Assert(err, jc.ErrorIsNil) 1010 c.Assert(entity.Life(), gc.Equals, life) 1011 } 1012 1013 func (s *applicationSuite) TestBlockApplicationDestroy(c *gc.C) { 1014 apiv16 := s.apiv16() 1015 s.AddTestingApplication(c, "dummy-application", s.AddTestingCharm(c, "dummy")) 1016 1017 // block remove-objects 1018 s.BlockRemoveObject(c, "TestBlockApplicationDestroy") 1019 err := apiv16.Destroy(params.ApplicationDestroy{ApplicationName: "dummy-application"}) 1020 s.AssertBlocked(c, err, "TestBlockApplicationDestroy") 1021 // Tests may have invalid application names. 1022 app, err := s.State.Application("dummy-application") 1023 if err == nil { 1024 // For valid application names, check that application is alive :-) 1025 assertLife(c, app, state.Alive) 1026 } 1027 } 1028 1029 func (s *applicationSuite) TestDestroyControllerApplicationNotAllowed(c *gc.C) { 1030 apiv16 := s.apiv16() 1031 s.AddTestingApplication(c, "controller-application", s.AddTestingCharm(c, "juju-controller")) 1032 1033 err := apiv16.Destroy(params.ApplicationDestroy{"controller-application"}) 1034 c.Assert(err, gc.ErrorMatches, "removing the controller application not supported") 1035 } 1036 1037 func (s *applicationSuite) TestDestroyPrincipalUnits(c *gc.C) { 1038 wordpress := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 1039 units := make([]*state.Unit, 5) 1040 for i := range units { 1041 unit, err := wordpress.AddUnit(state.AddUnitParams{}) 1042 c.Assert(err, jc.ErrorIsNil) 1043 unit.AssignToNewMachine() 1044 c.Assert(err, jc.ErrorIsNil) 1045 now := time.Now() 1046 sInfo := status.StatusInfo{ 1047 Status: status.Idle, 1048 Message: "", 1049 Since: &now, 1050 } 1051 err = unit.SetAgentStatus(sInfo) 1052 c.Assert(err, jc.ErrorIsNil) 1053 units[i] = unit 1054 } 1055 s.assertDestroyPrincipalUnits(c, units) 1056 } 1057 1058 func (s *applicationSuite) TestDestroySubordinateUnits(c *gc.C) { 1059 apiv16 := s.apiv16() 1060 wordpress := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 1061 wordpress0, err := wordpress.AddUnit(state.AddUnitParams{}) 1062 c.Assert(err, jc.ErrorIsNil) 1063 s.AddTestingApplication(c, "logging", s.AddTestingCharm(c, "logging")) 1064 eps, err := s.State.InferEndpoints("logging", "wordpress") 1065 c.Assert(err, jc.ErrorIsNil) 1066 rel, err := s.State.AddRelation(eps...) 1067 c.Assert(err, jc.ErrorIsNil) 1068 ru, err := rel.Unit(wordpress0) 1069 c.Assert(err, jc.ErrorIsNil) 1070 err = ru.EnterScope(nil) 1071 c.Assert(err, jc.ErrorIsNil) 1072 logging0, err := s.State.Unit("logging/0") 1073 c.Assert(err, jc.ErrorIsNil) 1074 1075 // Try to destroy the subordinate alone; check it fails. 1076 err = apiv16.DestroyUnits(params.DestroyApplicationUnits{ 1077 UnitNames: []string{"logging/0"}, 1078 }) 1079 c.Assert(err, gc.ErrorMatches, `no units were destroyed: unit "logging/0" is a subordinate, .*`) 1080 assertLife(c, logging0, state.Alive) 1081 1082 s.assertDestroySubordinateUnits(c, wordpress0, logging0) 1083 } 1084 1085 func (s *applicationSuite) assertDestroyPrincipalUnits(c *gc.C, units []*state.Unit) { 1086 apiv16 := s.apiv16() 1087 // Destroy 2 of them; check they become Dying. 1088 err := apiv16.DestroyUnits(params.DestroyApplicationUnits{ 1089 UnitNames: []string{"wordpress/0", "wordpress/1"}, 1090 }) 1091 c.Assert(err, jc.ErrorIsNil) 1092 assertLife(c, units[0], state.Dying) 1093 assertLife(c, units[1], state.Dying) 1094 1095 // Try to destroy an Alive one and a Dying one; check 1096 // it destroys the Alive one and ignores the Dying one. 1097 err = apiv16.DestroyUnits(params.DestroyApplicationUnits{ 1098 UnitNames: []string{"wordpress/2", "wordpress/0"}, 1099 }) 1100 c.Assert(err, jc.ErrorIsNil) 1101 assertLife(c, units[2], state.Dying) 1102 1103 // Try to destroy an Alive one along with a nonexistent one; check that 1104 // the valid instruction is followed but the invalid one is warned about. 1105 err = apiv16.DestroyUnits(params.DestroyApplicationUnits{ 1106 UnitNames: []string{"boojum/123", "wordpress/3"}, 1107 }) 1108 c.Assert(err, gc.ErrorMatches, `some units were not destroyed: unit "boojum/123" does not exist`) 1109 assertLife(c, units[3], state.Dying) 1110 1111 // Make one Dead, and destroy an Alive one alongside it; check no errors. 1112 wp0, err := s.State.Unit("wordpress/0") 1113 c.Assert(err, jc.ErrorIsNil) 1114 err = wp0.EnsureDead() 1115 c.Assert(err, jc.ErrorIsNil) 1116 err = apiv16.DestroyUnits(params.DestroyApplicationUnits{ 1117 UnitNames: []string{"wordpress/0", "wordpress/4"}, 1118 }) 1119 c.Assert(err, jc.ErrorIsNil) 1120 assertLife(c, units[0], state.Dead) 1121 assertLife(c, units[4], state.Dying) 1122 } 1123 1124 func (s *applicationSuite) setupDestroyPrincipalUnits(c *gc.C) []*state.Unit { 1125 units := make([]*state.Unit, 5) 1126 wordpress := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 1127 for i := range units { 1128 unit, err := wordpress.AddUnit(state.AddUnitParams{}) 1129 c.Assert(err, jc.ErrorIsNil) 1130 err = unit.AssignToNewMachine() 1131 c.Assert(err, jc.ErrorIsNil) 1132 now := time.Now() 1133 sInfo := status.StatusInfo{ 1134 Status: status.Idle, 1135 Message: "", 1136 Since: &now, 1137 } 1138 err = unit.SetAgentStatus(sInfo) 1139 c.Assert(err, jc.ErrorIsNil) 1140 units[i] = unit 1141 } 1142 return units 1143 } 1144 1145 func (s *applicationSuite) assertBlockedErrorAndLiveliness( 1146 c *gc.C, 1147 err error, 1148 msg string, 1149 living1 state.Living, 1150 living2 state.Living, 1151 living3 state.Living, 1152 living4 state.Living, 1153 ) { 1154 s.AssertBlocked(c, err, msg) 1155 assertLife(c, living1, state.Alive) 1156 assertLife(c, living2, state.Alive) 1157 assertLife(c, living3, state.Alive) 1158 assertLife(c, living4, state.Alive) 1159 } 1160 1161 func (s *applicationSuite) TestBlockChangesDestroyPrincipalUnits(c *gc.C) { 1162 apiv16 := s.apiv16() 1163 units := s.setupDestroyPrincipalUnits(c) 1164 s.BlockAllChanges(c, "TestBlockChangesDestroyPrincipalUnits") 1165 err := apiv16.DestroyUnits(params.DestroyApplicationUnits{ 1166 UnitNames: []string{"wordpress/0", "wordpress/1"}, 1167 }) 1168 s.assertBlockedErrorAndLiveliness(c, err, "TestBlockChangesDestroyPrincipalUnits", units[0], units[1], units[2], units[3]) 1169 } 1170 1171 func (s *applicationSuite) TestBlockRemoveDestroyPrincipalUnits(c *gc.C) { 1172 apiv16 := s.apiv16() 1173 units := s.setupDestroyPrincipalUnits(c) 1174 s.BlockRemoveObject(c, "TestBlockRemoveDestroyPrincipalUnits") 1175 err := apiv16.DestroyUnits(params.DestroyApplicationUnits{ 1176 UnitNames: []string{"wordpress/0", "wordpress/1"}, 1177 }) 1178 s.assertBlockedErrorAndLiveliness(c, err, "TestBlockRemoveDestroyPrincipalUnits", units[0], units[1], units[2], units[3]) 1179 } 1180 1181 func (s *applicationSuite) TestBlockDestroyDestroyPrincipalUnits(c *gc.C) { 1182 apiv16 := s.apiv16() 1183 units := s.setupDestroyPrincipalUnits(c) 1184 s.BlockDestroyModel(c, "TestBlockDestroyDestroyPrincipalUnits") 1185 err := apiv16.DestroyUnits(params.DestroyApplicationUnits{ 1186 UnitNames: []string{"wordpress/0", "wordpress/1"}, 1187 }) 1188 c.Assert(err, jc.ErrorIsNil) 1189 assertLife(c, units[0], state.Dying) 1190 assertLife(c, units[1], state.Dying) 1191 } 1192 1193 func (s *applicationSuite) assertDestroySubordinateUnits(c *gc.C, wordpress0, logging0 *state.Unit) { 1194 apiv16 := s.apiv16() 1195 // Try to destroy the principal and the subordinate together; check it warns 1196 // about the subordinate, but destroys the one it can. (The principal unit 1197 // agent will be responsible for destroying the subordinate.) 1198 err := apiv16.DestroyUnits(params.DestroyApplicationUnits{ 1199 UnitNames: []string{"wordpress/0", "logging/0"}, 1200 }) 1201 c.Assert(err, gc.ErrorMatches, `some units were not destroyed: unit "logging/0" is a subordinate, .*`) 1202 assertLife(c, wordpress0, state.Dying) 1203 assertLife(c, logging0, state.Alive) 1204 } 1205 1206 func (s *applicationSuite) TestBlockRemoveDestroySubordinateUnits(c *gc.C) { 1207 apiv16 := s.apiv16() 1208 wordpress := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 1209 wordpress0, err := wordpress.AddUnit(state.AddUnitParams{}) 1210 c.Assert(err, jc.ErrorIsNil) 1211 s.AddTestingApplication(c, "logging", s.AddTestingCharm(c, "logging")) 1212 eps, err := s.State.InferEndpoints("logging", "wordpress") 1213 c.Assert(err, jc.ErrorIsNil) 1214 rel, err := s.State.AddRelation(eps...) 1215 c.Assert(err, jc.ErrorIsNil) 1216 ru, err := rel.Unit(wordpress0) 1217 c.Assert(err, jc.ErrorIsNil) 1218 err = ru.EnterScope(nil) 1219 c.Assert(err, jc.ErrorIsNil) 1220 logging0, err := s.State.Unit("logging/0") 1221 c.Assert(err, jc.ErrorIsNil) 1222 1223 s.BlockRemoveObject(c, "TestBlockRemoveDestroySubordinateUnits") 1224 // Try to destroy the subordinate alone; check it fails. 1225 err = apiv16.DestroyUnits(params.DestroyApplicationUnits{ 1226 UnitNames: []string{"logging/0"}, 1227 }) 1228 s.AssertBlocked(c, err, "TestBlockRemoveDestroySubordinateUnits") 1229 assertLife(c, rel, state.Alive) 1230 assertLife(c, wordpress0, state.Alive) 1231 assertLife(c, logging0, state.Alive) 1232 1233 err = apiv16.DestroyUnits(params.DestroyApplicationUnits{ 1234 UnitNames: []string{"wordpress/0", "logging/0"}, 1235 }) 1236 s.AssertBlocked(c, err, "TestBlockRemoveDestroySubordinateUnits") 1237 assertLife(c, wordpress0, state.Alive) 1238 assertLife(c, logging0, state.Alive) 1239 assertLife(c, rel, state.Alive) 1240 } 1241 1242 func (s *applicationSuite) TestBlockChangesDestroySubordinateUnits(c *gc.C) { 1243 apiv16 := s.apiv16() 1244 wordpress := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 1245 wordpress0, err := wordpress.AddUnit(state.AddUnitParams{}) 1246 c.Assert(err, jc.ErrorIsNil) 1247 s.AddTestingApplication(c, "logging", s.AddTestingCharm(c, "logging")) 1248 eps, err := s.State.InferEndpoints("logging", "wordpress") 1249 c.Assert(err, jc.ErrorIsNil) 1250 rel, err := s.State.AddRelation(eps...) 1251 c.Assert(err, jc.ErrorIsNil) 1252 ru, err := rel.Unit(wordpress0) 1253 c.Assert(err, jc.ErrorIsNil) 1254 err = ru.EnterScope(nil) 1255 c.Assert(err, jc.ErrorIsNil) 1256 logging0, err := s.State.Unit("logging/0") 1257 c.Assert(err, jc.ErrorIsNil) 1258 1259 s.BlockAllChanges(c, "TestBlockChangesDestroySubordinateUnits") 1260 // Try to destroy the subordinate alone; check it fails. 1261 err = apiv16.DestroyUnits(params.DestroyApplicationUnits{ 1262 UnitNames: []string{"logging/0"}, 1263 }) 1264 s.AssertBlocked(c, err, "TestBlockChangesDestroySubordinateUnits") 1265 assertLife(c, rel, state.Alive) 1266 assertLife(c, wordpress0, state.Alive) 1267 assertLife(c, logging0, state.Alive) 1268 1269 err = apiv16.DestroyUnits(params.DestroyApplicationUnits{ 1270 UnitNames: []string{"wordpress/0", "logging/0"}, 1271 }) 1272 s.AssertBlocked(c, err, "TestBlockChangesDestroySubordinateUnits") 1273 assertLife(c, wordpress0, state.Alive) 1274 assertLife(c, logging0, state.Alive) 1275 assertLife(c, rel, state.Alive) 1276 } 1277 1278 func (s *applicationSuite) TestBlockDestroyDestroySubordinateUnits(c *gc.C) { 1279 apiv16 := s.apiv16() 1280 wordpress := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 1281 wordpress0, err := wordpress.AddUnit(state.AddUnitParams{}) 1282 c.Assert(err, jc.ErrorIsNil) 1283 s.AddTestingApplication(c, "logging", s.AddTestingCharm(c, "logging")) 1284 eps, err := s.State.InferEndpoints("logging", "wordpress") 1285 c.Assert(err, jc.ErrorIsNil) 1286 rel, err := s.State.AddRelation(eps...) 1287 c.Assert(err, jc.ErrorIsNil) 1288 ru, err := rel.Unit(wordpress0) 1289 c.Assert(err, jc.ErrorIsNil) 1290 err = ru.EnterScope(nil) 1291 c.Assert(err, jc.ErrorIsNil) 1292 logging0, err := s.State.Unit("logging/0") 1293 c.Assert(err, jc.ErrorIsNil) 1294 1295 s.BlockDestroyModel(c, "TestBlockDestroyDestroySubordinateUnits") 1296 // Try to destroy the subordinate alone; check it fails. 1297 err = apiv16.DestroyUnits(params.DestroyApplicationUnits{ 1298 UnitNames: []string{"logging/0"}, 1299 }) 1300 c.Assert(err, gc.ErrorMatches, `no units were destroyed: unit "logging/0" is a subordinate, .*`) 1301 assertLife(c, logging0, state.Alive) 1302 1303 s.assertDestroySubordinateUnits(c, wordpress0, logging0) 1304 } 1305 1306 func (s *applicationSuite) TestClientSetApplicationConstraints(c *gc.C) { 1307 app := s.AddTestingApplication(c, "dummy", s.AddTestingCharm(c, "dummy")) 1308 1309 // Update constraints for the application. 1310 cons, err := constraints.Parse("mem=4096", "cores=2") 1311 c.Assert(err, jc.ErrorIsNil) 1312 err = s.applicationAPI.SetConstraints(params.SetConstraints{ApplicationName: "dummy", Constraints: cons}) 1313 c.Assert(err, jc.ErrorIsNil) 1314 1315 // Ensure the constraints have been correctly updated. 1316 obtained, err := app.Constraints() 1317 c.Assert(err, jc.ErrorIsNil) 1318 c.Assert(obtained, gc.DeepEquals, cons) 1319 } 1320 1321 func (s *applicationSuite) setupSetApplicationConstraints(c *gc.C) (*state.Application, constraints.Value) { 1322 app := s.AddTestingApplication(c, "dummy", s.AddTestingCharm(c, "dummy")) 1323 // Update constraints for the application. 1324 cons, err := constraints.Parse("mem=4096", "cores=2") 1325 c.Assert(err, jc.ErrorIsNil) 1326 return app, cons 1327 } 1328 1329 func (s *applicationSuite) assertSetApplicationConstraints(c *gc.C, application *state.Application, cons constraints.Value) { 1330 err := s.applicationAPI.SetConstraints(params.SetConstraints{ApplicationName: "dummy", Constraints: cons}) 1331 c.Assert(err, jc.ErrorIsNil) 1332 // Ensure the constraints have been correctly updated. 1333 obtained, err := application.Constraints() 1334 c.Assert(err, jc.ErrorIsNil) 1335 c.Assert(obtained, gc.DeepEquals, cons) 1336 } 1337 1338 func (s *applicationSuite) assertSetApplicationConstraintsBlocked(c *gc.C, msg string, application *state.Application, cons constraints.Value) { 1339 err := s.applicationAPI.SetConstraints(params.SetConstraints{ApplicationName: "dummy", Constraints: cons}) 1340 s.AssertBlocked(c, err, msg) 1341 } 1342 1343 func (s *applicationSuite) TestBlockDestroySetApplicationConstraints(c *gc.C) { 1344 app, cons := s.setupSetApplicationConstraints(c) 1345 s.BlockDestroyModel(c, "TestBlockDestroySetApplicationConstraints") 1346 s.assertSetApplicationConstraints(c, app, cons) 1347 } 1348 1349 func (s *applicationSuite) TestBlockRemoveSetApplicationConstraints(c *gc.C) { 1350 app, cons := s.setupSetApplicationConstraints(c) 1351 s.BlockRemoveObject(c, "TestBlockRemoveSetApplicationConstraints") 1352 s.assertSetApplicationConstraints(c, app, cons) 1353 } 1354 1355 func (s *applicationSuite) TestBlockChangesSetApplicationConstraints(c *gc.C) { 1356 app, cons := s.setupSetApplicationConstraints(c) 1357 s.BlockAllChanges(c, "TestBlockChangesSetApplicationConstraints") 1358 s.assertSetApplicationConstraintsBlocked(c, "TestBlockChangesSetApplicationConstraints", app, cons) 1359 } 1360 1361 func (s *applicationSuite) TestClientGetApplicationConstraints(c *gc.C) { 1362 fooConstraints := constraints.MustParse("arch=amd64", "mem=4G") 1363 s.Factory.MakeApplication(c, &factory.ApplicationParams{ 1364 Name: "foo", 1365 Constraints: fooConstraints, 1366 }) 1367 barConstraints := constraints.MustParse("arch=amd64", "mem=128G", "cores=64") 1368 s.Factory.MakeApplication(c, &factory.ApplicationParams{ 1369 Name: "bar", 1370 Constraints: barConstraints, 1371 }) 1372 1373 results, err := s.applicationAPI.GetConstraints(params.Entities{ 1374 Entities: []params.Entity{ 1375 {Tag: "wat"}, {Tag: "machine-0"}, {Tag: "user-foo"}, 1376 {Tag: "application-foo"}, {Tag: "application-bar"}, {Tag: "application-wat"}, 1377 }, 1378 }) 1379 c.Assert(err, jc.ErrorIsNil) 1380 c.Assert(results, jc.DeepEquals, params.ApplicationGetConstraintsResults{ 1381 Results: []params.ApplicationConstraint{ 1382 { 1383 Error: ¶ms.Error{Message: `"wat" is not a valid tag`}, 1384 }, { 1385 Error: ¶ms.Error{Message: `unexpected tag type, expected application, got machine`}, 1386 }, { 1387 Error: ¶ms.Error{Message: `unexpected tag type, expected application, got user`}, 1388 }, { 1389 Constraints: fooConstraints, 1390 }, { 1391 Constraints: barConstraints, 1392 }, { 1393 Error: ¶ms.Error{Message: `application "wat" not found`, Code: "not found"}, 1394 }, 1395 }}) 1396 } 1397 1398 func (s *applicationSuite) checkEndpoints(c *gc.C, mysqlAppName string, endpoints map[string]params.CharmRelation) { 1399 c.Assert(endpoints["wordpress"], gc.DeepEquals, params.CharmRelation{ 1400 Name: "db", 1401 Role: "requirer", 1402 Interface: "mysql", 1403 Optional: false, 1404 Limit: 1, 1405 Scope: "global", 1406 }) 1407 ep := params.CharmRelation{ 1408 Name: "server", 1409 Role: "provider", 1410 Interface: "mysql", 1411 Scope: "global", 1412 } 1413 // Remote applications don't use scope. 1414 if mysqlAppName == "hosted-mysql" { 1415 ep.Scope = "" 1416 } 1417 c.Assert(endpoints[mysqlAppName], gc.DeepEquals, ep) 1418 } 1419 1420 func (s *applicationSuite) setupRelationScenario(c *gc.C) { 1421 s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 1422 s.AddTestingApplication(c, "logging", s.AddTestingCharm(c, "logging")) 1423 eps, err := s.State.InferEndpoints("logging", "wordpress") 1424 c.Assert(err, jc.ErrorIsNil) 1425 _, err = s.State.AddRelation(eps...) 1426 c.Assert(err, jc.ErrorIsNil) 1427 } 1428 1429 func (s *applicationSuite) assertAddRelation(c *gc.C, endpoints, viaCIDRs []string) { 1430 s.setupRelationScenario(c) 1431 1432 res, err := s.applicationAPI.AddRelation(params.AddRelation{Endpoints: endpoints, ViaCIDRs: viaCIDRs}) 1433 c.Assert(err, jc.ErrorIsNil) 1434 // Show that the relation was added. 1435 wpApp, err := s.State.Application("wordpress") 1436 c.Assert(err, jc.ErrorIsNil) 1437 rels, err := wpApp.Relations() 1438 c.Assert(err, jc.ErrorIsNil) 1439 // There are 2 relations - the logging-wordpress one set up in the 1440 // scenario and the one created in this test. 1441 c.Assert(len(rels), gc.Equals, 2) 1442 1443 // We may be related to a local application or a remote offer 1444 // or an application in another model. 1445 var mySqlApplication state.ApplicationEntity 1446 mySqlApplication, err = s.State.RemoteApplication("hosted-mysql") 1447 if errors.IsNotFound(err) { 1448 mySqlApplication, err = s.State.RemoteApplication("othermysql") 1449 if errors.IsNotFound(err) { 1450 mySqlApplication, err = s.State.Application("mysql") 1451 c.Assert(err, jc.ErrorIsNil) 1452 s.checkEndpoints(c, "mysql", res.Endpoints) 1453 } else { 1454 c.Assert(err, jc.ErrorIsNil) 1455 s.checkEndpoints(c, "othermysql", res.Endpoints) 1456 } 1457 } else { 1458 c.Assert(err, jc.ErrorIsNil) 1459 s.checkEndpoints(c, "hosted-mysql", res.Endpoints) 1460 } 1461 c.Assert(err, jc.ErrorIsNil) 1462 rels, err = mySqlApplication.Relations() 1463 c.Assert(err, jc.ErrorIsNil) 1464 c.Assert(len(rels), gc.Equals, 1) 1465 } 1466 1467 func (s *applicationSuite) TestSuccessfullyAddRelation(c *gc.C) { 1468 endpoints := []string{"wordpress", "mysql"} 1469 s.assertAddRelation(c, endpoints, nil) 1470 } 1471 1472 func (s *applicationSuite) TestBlockDestroyAddRelation(c *gc.C) { 1473 s.BlockDestroyModel(c, "TestBlockDestroyAddRelation") 1474 s.assertAddRelation(c, []string{"wordpress", "mysql"}, nil) 1475 } 1476 func (s *applicationSuite) TestBlockRemoveAddRelation(c *gc.C) { 1477 s.BlockRemoveObject(c, "TestBlockRemoveAddRelation") 1478 s.assertAddRelation(c, []string{"wordpress", "mysql"}, nil) 1479 } 1480 1481 func (s *applicationSuite) TestBlockChangesAddRelation(c *gc.C) { 1482 s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 1483 s.BlockAllChanges(c, "TestBlockChangesAddRelation") 1484 _, err := s.applicationAPI.AddRelation(params.AddRelation{Endpoints: []string{"wordpress", "mysql"}}) 1485 s.AssertBlocked(c, err, "TestBlockChangesAddRelation") 1486 } 1487 1488 func (s *applicationSuite) TestSuccessfullyAddRelationSwapped(c *gc.C) { 1489 // Show that the order of the applications listed in the AddRelation call 1490 // does not matter. This is a repeat of the previous test with the application 1491 // names swapped. 1492 endpoints := []string{"mysql", "wordpress"} 1493 s.assertAddRelation(c, endpoints, nil) 1494 } 1495 1496 func (s *applicationSuite) TestCallWithOnlyOneEndpoint(c *gc.C) { 1497 s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 1498 endpoints := []string{"wordpress"} 1499 _, err := s.applicationAPI.AddRelation(params.AddRelation{Endpoints: endpoints}) 1500 c.Assert(err, gc.ErrorMatches, "no relations found") 1501 } 1502 1503 func (s *applicationSuite) TestCallWithOneEndpointTooMany(c *gc.C) { 1504 s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 1505 s.AddTestingApplication(c, "logging", s.AddTestingCharm(c, "logging")) 1506 endpoints := []string{"wordpress", "mysql", "logging"} 1507 _, err := s.applicationAPI.AddRelation(params.AddRelation{Endpoints: endpoints}) 1508 c.Assert(err, gc.ErrorMatches, "cannot relate 3 endpoints") 1509 } 1510 1511 func (s *applicationSuite) TestAddAlreadyAddedRelation(c *gc.C) { 1512 s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 1513 // Add a relation between wordpress and mysql. 1514 endpoints := []string{"wordpress", "mysql"} 1515 eps, err := s.State.InferEndpoints(endpoints...) 1516 c.Assert(err, jc.ErrorIsNil) 1517 _, err = s.State.AddRelation(eps...) 1518 c.Assert(err, jc.ErrorIsNil) 1519 // And try to add it again. 1520 _, err = s.applicationAPI.AddRelation(params.AddRelation{Endpoints: endpoints}) 1521 c.Assert(err, gc.ErrorMatches, `cannot add relation "wordpress:db mysql:server": relation wordpress:db mysql:server`) 1522 } 1523 1524 func (s *applicationSuite) setupRemoteApplication(c *gc.C) { 1525 results, err := s.applicationAPI.Consume(params.ConsumeApplicationArgsV5{ 1526 Args: []params.ConsumeApplicationArgV5{ 1527 {ApplicationOfferDetailsV5: params.ApplicationOfferDetailsV5{ 1528 SourceModelTag: testing.ModelTag.String(), 1529 OfferName: "hosted-mysql", 1530 OfferUUID: "hosted-mysql-uuid", 1531 ApplicationDescription: "A pretty popular database", 1532 Endpoints: []params.RemoteEndpoint{ 1533 {Name: "server", Interface: "mysql", Role: "provider"}, 1534 }, 1535 }}, 1536 }, 1537 }) 1538 c.Assert(err, jc.ErrorIsNil) 1539 c.Assert(results.OneError(), gc.IsNil) 1540 } 1541 1542 func (s *applicationSuite) TestAddRemoteRelation(c *gc.C) { 1543 s.setupRemoteApplication(c) 1544 // There's already a wordpress in the scenario this assertion sets up. 1545 s.assertAddRelation(c, []string{"wordpress", "hosted-mysql"}, nil) 1546 } 1547 1548 func (s *applicationSuite) TestAddRemoteRelationWithRelName(c *gc.C) { 1549 s.setupRemoteApplication(c) 1550 s.assertAddRelation(c, []string{"wordpress", "hosted-mysql:server"}, nil) 1551 } 1552 1553 func (s *applicationSuite) TestAddRemoteRelationVia(c *gc.C) { 1554 s.setupRemoteApplication(c) 1555 s.assertAddRelation(c, []string{"wordpress", "hosted-mysql:server"}, []string{"192.168.0.0/16"}) 1556 1557 rel, err := s.State.KeyRelation("wordpress:db hosted-mysql:server") 1558 c.Assert(err, jc.ErrorIsNil) 1559 w := rel.WatchRelationEgressNetworks() 1560 defer statetesting.AssertStop(c, w) 1561 wc := statetesting.NewStringsWatcherC(c, w) 1562 wc.AssertChange("192.168.0.0/16") 1563 wc.AssertNoChange() 1564 } 1565 1566 func (s *applicationSuite) TestAddRemoteRelationOnlyOneEndpoint(c *gc.C) { 1567 s.setupRemoteApplication(c) 1568 endpoints := []string{"hosted-mysql"} 1569 _, err := s.applicationAPI.AddRelation(params.AddRelation{Endpoints: endpoints}) 1570 c.Assert(err, gc.ErrorMatches, "no relations found") 1571 } 1572 1573 func (s *applicationSuite) TestAlreadyAddedRemoteRelation(c *gc.C) { 1574 s.setupRemoteApplication(c) 1575 endpoints := []string{"wordpress", "hosted-mysql"} 1576 s.assertAddRelation(c, endpoints, nil) 1577 1578 // And try to add it again. 1579 _, err := s.applicationAPI.AddRelation(params.AddRelation{Endpoints: endpoints}) 1580 c.Assert(err, gc.ErrorMatches, regexp.QuoteMeta(`cannot add relation "wordpress:db hosted-mysql:server": relation wordpress:db hosted-mysql:server`)) 1581 } 1582 1583 func (s *applicationSuite) TestRemoteRelationInvalidEndpoint(c *gc.C) { 1584 s.setupRemoteApplication(c) 1585 s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 1586 1587 endpoints := []string{"wordpress", "hosted-mysql:nope"} 1588 _, err := s.applicationAPI.AddRelation(params.AddRelation{Endpoints: endpoints}) 1589 c.Assert(err, gc.ErrorMatches, `saas application "hosted-mysql" has no "nope" relation`) 1590 } 1591 1592 func (s *applicationSuite) TestRemoteRelationNoMatchingEndpoint(c *gc.C) { 1593 results, err := s.applicationAPI.Consume(params.ConsumeApplicationArgsV5{ 1594 Args: []params.ConsumeApplicationArgV5{ 1595 {ApplicationOfferDetailsV5: params.ApplicationOfferDetailsV5{ 1596 SourceModelTag: testing.ModelTag.String(), 1597 OfferName: "hosted-db2", 1598 OfferUUID: "hosted-db2-uuid", 1599 Endpoints: []params.RemoteEndpoint{ 1600 {Name: "database", Interface: "db2", Role: "provider"}, 1601 }, 1602 }}, 1603 }, 1604 }) 1605 c.Assert(err, jc.ErrorIsNil) 1606 c.Assert(results.OneError(), gc.IsNil) 1607 1608 s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 1609 endpoints := []string{"wordpress", "hosted-db2"} 1610 _, err = s.applicationAPI.AddRelation(params.AddRelation{Endpoints: endpoints}) 1611 c.Assert(err, gc.ErrorMatches, "no relations found") 1612 } 1613 1614 func (s *applicationSuite) TestRemoteRelationApplicationNotFound(c *gc.C) { 1615 s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 1616 endpoints := []string{"wordpress", "unknown"} 1617 _, err := s.applicationAPI.AddRelation(params.AddRelation{Endpoints: endpoints}) 1618 c.Assert(err, gc.ErrorMatches, `application "unknown" not found`) 1619 } 1620 1621 // addCharmToState emulates the side-effects of an AddCharm call so that the 1622 // deploy tests in the suite can still work even though the AddCharmX calls 1623 // have been updated to return NotSupported errors for Juju 3. 1624 func (s *applicationSuite) addCharmToState(c *gc.C, charmURL string, name string) (string, charm.Charm) { 1625 curl := charm.MustParseURL(charmURL) 1626 1627 if curl.Revision < 0 { 1628 base := curl.String() 1629 1630 if rev, found := s.lastKnownRev[base]; found { 1631 curl.Revision = rev + 1 1632 } else { 1633 curl.Revision = 0 1634 } 1635 1636 s.lastKnownRev[base] = curl.Revision 1637 } 1638 1639 _, err := s.State.PrepareCharmUpload(charmURL) 1640 c.Assert(err, jc.ErrorIsNil) 1641 1642 ch, err := charm.ReadCharmArchive( 1643 testcharms.RepoWithSeries("quantal").CharmArchivePath(c.MkDir(), name)) 1644 c.Assert(err, jc.ErrorIsNil) 1645 1646 _, err = s.State.UpdateUploadedCharm(state.CharmInfo{ 1647 Charm: ch, 1648 ID: charmURL, 1649 StoragePath: fmt.Sprintf("charms/%s", name), 1650 SHA256: "1234", 1651 Version: ch.Version(), 1652 }) 1653 c.Assert(err, jc.ErrorIsNil) 1654 1655 return charmURL, ch 1656 } 1657 1658 func (s *applicationSuite) TestValidateSecretConfig(c *gc.C) { 1659 chCfg := &charm.Config{ 1660 Options: map[string]charm.Option{ 1661 "foo": {Type: "secret"}, 1662 }, 1663 } 1664 cfg := charm.Settings{ 1665 "foo": "bar", 1666 } 1667 err := application.ValidateSecretConfig(chCfg, cfg) 1668 c.Assert(err, gc.ErrorMatches, `invalid secret URI for option "foo": secret URI "bar" not valid`) 1669 1670 cfg = charm.Settings{"foo": "secret:cj4v5vm78ohs79o84r4g"} 1671 err = application.ValidateSecretConfig(chCfg, cfg) 1672 c.Assert(err, jc.ErrorIsNil) 1673 }