github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/state/application_test.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package state_test 5 6 import ( 7 "fmt" 8 "sort" 9 "strings" 10 "time" 11 12 "github.com/juju/charm/v12" 13 "github.com/juju/collections/set" 14 "github.com/juju/errors" 15 "github.com/juju/loggo" 16 "github.com/juju/mgo/v3/bson" 17 "github.com/juju/mgo/v3/txn" 18 "github.com/juju/names/v5" 19 jc "github.com/juju/testing/checkers" 20 jujutxn "github.com/juju/txn/v3" 21 "github.com/juju/version/v2" 22 gc "gopkg.in/check.v1" 23 "gopkg.in/juju/environschema.v1" 24 25 "github.com/juju/juju/core/arch" 26 corearch "github.com/juju/juju/core/arch" 27 corebase "github.com/juju/juju/core/base" 28 corecharm "github.com/juju/juju/core/charm" 29 "github.com/juju/juju/core/config" 30 "github.com/juju/juju/core/constraints" 31 "github.com/juju/juju/core/crossmodel" 32 "github.com/juju/juju/core/model" 33 "github.com/juju/juju/core/network" 34 "github.com/juju/juju/core/network/firewall" 35 resourcetesting "github.com/juju/juju/core/resources/testing" 36 "github.com/juju/juju/core/secrets" 37 "github.com/juju/juju/core/status" 38 "github.com/juju/juju/state" 39 stateerrors "github.com/juju/juju/state/errors" 40 "github.com/juju/juju/state/testing" 41 statetesting "github.com/juju/juju/state/testing" 42 "github.com/juju/juju/storage" 43 "github.com/juju/juju/storage/poolmanager" 44 "github.com/juju/juju/storage/provider/dummy" 45 "github.com/juju/juju/testcharms" 46 coretesting "github.com/juju/juju/testing" 47 "github.com/juju/juju/testing/factory" 48 jujuversion "github.com/juju/juju/version" 49 ) 50 51 type ApplicationSuite struct { 52 ConnSuite 53 54 charm *state.Charm 55 mysql *state.Application 56 } 57 58 var _ = gc.Suite(&ApplicationSuite{}) 59 60 func (s *ApplicationSuite) SetUpTest(c *gc.C) { 61 s.ConnSuite.SetUpTest(c) 62 s.policy.GetConstraintsValidator = func() (constraints.Validator, error) { 63 validator := constraints.NewValidator() 64 validator.RegisterConflicts([]string{constraints.InstanceType}, []string{constraints.Mem}) 65 validator.RegisterUnsupported([]string{constraints.CpuPower}) 66 return validator, nil 67 } 68 s.charm = s.AddTestingCharm(c, "mysql") 69 s.mysql = s.AddTestingApplication(c, "mysql", s.charm) 70 // Before we get into the tests, ensure that all the creation events have flowed through the system. 71 s.WaitForModelWatchersIdle(c, s.Model.UUID()) 72 } 73 74 func (s *ApplicationSuite) assertNeedsCleanup(c *gc.C) { 75 dirty, err := s.State.NeedsCleanup() 76 c.Assert(err, jc.ErrorIsNil) 77 c.Assert(dirty, jc.IsTrue) 78 } 79 80 func (s *ApplicationSuite) assertNoCleanup(c *gc.C) { 81 dirty, err := s.State.NeedsCleanup() 82 c.Assert(err, jc.ErrorIsNil) 83 c.Assert(dirty, jc.IsFalse) 84 } 85 86 func (s *ApplicationSuite) TestSetCharm(c *gc.C) { 87 ch, force, err := s.mysql.Charm() 88 c.Assert(err, jc.ErrorIsNil) 89 c.Assert(ch.URL(), gc.DeepEquals, s.charm.URL()) 90 c.Assert(force, jc.IsFalse) 91 url, force := s.mysql.CharmURL() 92 c.Assert(*url, gc.DeepEquals, s.charm.URL()) 93 c.Assert(force, jc.IsFalse) 94 95 // Add a compatible charm and force it. 96 sch := s.AddMetaCharm(c, "mysql", metaBase, 2) 97 98 cfg := state.SetCharmConfig{ 99 Charm: sch, 100 CharmOrigin: defaultCharmOrigin(sch.URL()), 101 ForceUnits: true, 102 } 103 err = s.mysql.SetCharm(cfg) 104 c.Assert(err, jc.ErrorIsNil) 105 ch, force, err = s.mysql.Charm() 106 c.Assert(err, jc.ErrorIsNil) 107 c.Assert(ch.URL(), gc.DeepEquals, sch.URL()) 108 c.Assert(force, jc.IsTrue) 109 url, force = s.mysql.CharmURL() 110 c.Assert(*url, gc.DeepEquals, sch.URL()) 111 c.Assert(force, jc.IsTrue) 112 } 113 114 func (s *ApplicationSuite) TestSetCharmCharmOrigin(c *gc.C) { 115 // Add a compatible charm. 116 sch := s.AddMetaCharm(c, "mysql", metaBase, 2) 117 rev := sch.Revision() 118 origin := &state.CharmOrigin{ 119 Source: "charm-hub", 120 Revision: &rev, 121 Channel: &state.Channel{Risk: "stable"}, 122 Platform: &state.Platform{ 123 OS: "ubuntu", 124 Channel: "22.04/stable", 125 }, 126 } 127 cfg := state.SetCharmConfig{ 128 Charm: sch, 129 CharmOrigin: origin, 130 } 131 err := s.mysql.SetCharm(cfg) 132 c.Assert(err, jc.ErrorIsNil) 133 err = s.mysql.Refresh() 134 c.Assert(err, jc.ErrorIsNil) 135 obtainedOrigin := s.mysql.CharmOrigin() 136 c.Assert(obtainedOrigin, gc.DeepEquals, origin) 137 } 138 139 func (s *ApplicationSuite) TestSetCharmUpdateChannelURLNoChange(c *gc.C) { 140 sch := s.AddMetaCharm(c, "mysql", metaBase, 2) 141 142 origin := defaultCharmOrigin(sch.URL()) 143 // This is a workaround, AddCharm creates a local 144 // charm, which cannot have a channel in the CharmOrigin. 145 // However, we need to test changing the channel only. 146 origin.Source = "charm-hub" 147 origin.Channel = &state.Channel{ 148 Risk: "stable", 149 } 150 cfg := state.SetCharmConfig{ 151 Charm: sch, 152 CharmOrigin: origin, 153 } 154 err := s.mysql.SetCharm(cfg) 155 c.Assert(err, jc.ErrorIsNil) 156 mOrigin := s.mysql.CharmOrigin() 157 c.Assert(mOrigin.Channel, gc.NotNil) 158 c.Assert(mOrigin.Channel.Risk, gc.DeepEquals, "stable") 159 160 cfg.CharmOrigin.Channel.Risk = "candidate" 161 err = s.mysql.SetCharm(cfg) 162 c.Assert(err, jc.ErrorIsNil) 163 c.Assert(s.mysql.CharmOrigin().Channel.Risk, gc.DeepEquals, "candidate") 164 } 165 166 func (s *ApplicationSuite) TestLXDProfileSetCharm(c *gc.C) { 167 charm := s.AddTestingCharm(c, "lxd-profile") 168 app := s.AddTestingApplication(c, "lxd-profile", charm) 169 170 c.Assert(charm.LXDProfile(), gc.NotNil) 171 172 ch, force, err := app.Charm() 173 c.Assert(err, jc.ErrorIsNil) 174 c.Assert(ch.URL(), gc.DeepEquals, charm.URL()) 175 c.Assert(force, jc.IsFalse) 176 c.Assert(charm.LXDProfile(), gc.DeepEquals, ch.LXDProfile()) 177 178 url, force := app.CharmURL() 179 c.Assert(*url, gc.DeepEquals, charm.URL()) 180 c.Assert(force, jc.IsFalse) 181 182 sch := s.AddMetaCharm(c, "lxd-profile", lxdProfileMetaBase, 2) 183 184 cfg := state.SetCharmConfig{ 185 Charm: sch, 186 CharmOrigin: defaultCharmOrigin(ch.URL()), 187 ForceUnits: true, 188 } 189 err = app.SetCharm(cfg) 190 c.Assert(err, jc.ErrorIsNil) 191 ch, force, err = app.Charm() 192 c.Assert(err, jc.ErrorIsNil) 193 c.Assert(ch.URL(), gc.DeepEquals, sch.URL()) 194 c.Assert(force, jc.IsTrue) 195 url, force = app.CharmURL() 196 c.Assert(*url, gc.DeepEquals, sch.URL()) 197 c.Assert(force, jc.IsTrue) 198 c.Assert(charm.LXDProfile(), gc.DeepEquals, ch.LXDProfile()) 199 } 200 201 func (s *ApplicationSuite) TestLXDProfileFailSetCharm(c *gc.C) { 202 charm := s.AddTestingCharm(c, "lxd-profile-fail") 203 app := s.AddTestingApplication(c, "lxd-profile-fail", charm) 204 205 c.Assert(charm.LXDProfile(), gc.NotNil) 206 207 ch, force, err := app.Charm() 208 c.Assert(err, jc.ErrorIsNil) 209 c.Assert(ch.URL(), gc.DeepEquals, charm.URL()) 210 c.Assert(force, jc.IsFalse) 211 c.Assert(charm.LXDProfile(), gc.DeepEquals, ch.LXDProfile()) 212 213 url, force := app.CharmURL() 214 c.Assert(*url, gc.DeepEquals, charm.URL()) 215 c.Assert(force, jc.IsFalse) 216 217 sch := s.AddMetaCharm(c, "lxd-profile-fail", lxdProfileMetaBase, 2) 218 219 cfg := state.SetCharmConfig{ 220 Charm: sch, 221 CharmOrigin: defaultCharmOrigin(ch.URL()), 222 ForceUnits: true, 223 } 224 err = app.SetCharm(cfg) 225 c.Assert(err, gc.ErrorMatches, ".*validating lxd profile: invalid lxd-profile\\.yaml.*") 226 } 227 228 func (s *ApplicationSuite) TestLXDProfileFailWithForceSetCharm(c *gc.C) { 229 charm := s.AddTestingCharm(c, "lxd-profile-fail") 230 app := s.AddTestingApplication(c, "lxd-profile-fail", charm) 231 232 c.Assert(charm.LXDProfile(), gc.NotNil) 233 234 ch, force, err := app.Charm() 235 c.Assert(err, jc.ErrorIsNil) 236 c.Assert(ch.URL(), gc.DeepEquals, charm.URL()) 237 c.Assert(force, jc.IsFalse) 238 c.Assert(charm.LXDProfile(), gc.DeepEquals, ch.LXDProfile()) 239 240 url, force := app.CharmURL() 241 c.Assert(*url, gc.DeepEquals, charm.URL()) 242 c.Assert(force, jc.IsFalse) 243 244 sch := s.AddMetaCharm(c, "lxd-profile-fail", lxdProfileMetaBase, 2) 245 246 cfg := state.SetCharmConfig{ 247 Charm: sch, 248 CharmOrigin: defaultCharmOrigin(ch.URL()), 249 Force: true, 250 ForceUnits: true, 251 } 252 err = app.SetCharm(cfg) 253 c.Assert(err, jc.ErrorIsNil) 254 ch, force, err = app.Charm() 255 c.Assert(err, jc.ErrorIsNil) 256 c.Assert(ch.URL(), gc.DeepEquals, sch.URL()) 257 c.Assert(force, jc.IsTrue) 258 url, force = app.CharmURL() 259 c.Assert(*url, gc.DeepEquals, sch.URL()) 260 c.Assert(force, jc.IsTrue) 261 c.Assert(charm.LXDProfile(), gc.DeepEquals, ch.LXDProfile()) 262 } 263 264 func (s *ApplicationSuite) TestCAASSetCharm(c *gc.C) { 265 st := s.Factory.MakeModel(c, &factory.ModelParams{ 266 Name: "caas-model", 267 Type: state.ModelTypeCAAS, 268 }) 269 defer st.Close() 270 f := factory.NewFactory(st, s.StatePool) 271 ch := f.MakeCharm(c, &factory.CharmParams{Name: "mysql", Series: "kubernetes"}) 272 app := f.MakeApplication(c, &factory.ApplicationParams{Name: "mysql", Charm: ch}) 273 274 // Add a compatible charm and force it. 275 sch := state.AddCustomCharm(c, st, "mysql", "metadata.yaml", metaBaseCAAS, "kubernetes", 2) 276 277 cfg := state.SetCharmConfig{ 278 Charm: sch, 279 CharmOrigin: defaultCharmOrigin(ch.URL()), 280 ForceUnits: true, 281 } 282 err := app.SetCharm(cfg) 283 c.Assert(err, jc.ErrorIsNil) 284 ch, force, err := app.Charm() 285 c.Assert(err, jc.ErrorIsNil) 286 c.Assert(ch.URL(), gc.DeepEquals, sch.URL()) 287 c.Assert(force, jc.IsTrue) 288 } 289 290 func (s *ApplicationSuite) TestCAASSetCharmRequireNoUnits(c *gc.C) { 291 st := s.Factory.MakeModel(c, &factory.ModelParams{ 292 Name: "caas-model", 293 Type: state.ModelTypeCAAS, 294 }) 295 defer st.Close() 296 f := factory.NewFactory(st, s.StatePool) 297 ch := f.MakeCharm(c, &factory.CharmParams{Name: "mysql", Series: "kubernetes"}) 298 app := f.MakeApplication(c, &factory.ApplicationParams{Name: "mysql", Charm: ch, DesiredScale: 1}) 299 300 // Add a compatible charm and force it. 301 sch := state.AddCustomCharm(c, st, "mysql", "metadata.yaml", metaBaseCAAS, "kubernetes", 2) 302 303 cfg := state.SetCharmConfig{ 304 Charm: sch, 305 CharmOrigin: defaultCharmOrigin(ch.URL()), 306 ForceUnits: true, 307 RequireNoUnits: true, 308 } 309 err := app.SetCharm(cfg) 310 c.Assert(err, gc.ErrorMatches, `.*application should not have units`) 311 } 312 313 func (s *ApplicationSuite) TestCAASSetCharmNewDeploymentFails(c *gc.C) { 314 st := s.Factory.MakeModel(c, &factory.ModelParams{ 315 Name: "caas-model", 316 Type: state.ModelTypeCAAS, 317 }) 318 defer st.Close() 319 f := factory.NewFactory(st, s.StatePool) 320 ch := f.MakeCharm(c, &factory.CharmParams{Name: "gitlab", Series: "kubernetes"}) 321 app := f.MakeApplication(c, &factory.ApplicationParams{Name: "gitlab", Charm: ch}) 322 323 // Create a charm with new deployment info in metadata. 324 metaYaml := ` 325 name: gitlab 326 summary: test 327 description: test 328 provides: 329 website: 330 interface: http 331 requires: 332 db: 333 interface: mysql 334 series: 335 - kubernetes 336 deployment: 337 type: stateful 338 service: loadbalancer 339 `[1:] 340 newCh := state.AddCustomCharm(c, st, "gitlab", "metadata.yaml", metaYaml, "kubernetes", 2) 341 cfg := state.SetCharmConfig{ 342 Charm: newCh, 343 CharmOrigin: defaultCharmOrigin(newCh.URL()), 344 ForceUnits: true, 345 } 346 err := app.SetCharm(cfg) 347 c.Assert(err, gc.ErrorMatches, `cannot upgrade application "gitlab" to charm "local:kubernetes/kubernetes-gitlab-2": cannot change a charm's deployment info`) 348 } 349 350 func (s *ApplicationSuite) TestCAASSetCharmNewDeploymentTypeFails(c *gc.C) { 351 st := s.Factory.MakeModel(c, &factory.ModelParams{ 352 Name: "caas-model", 353 Type: state.ModelTypeCAAS, 354 }) 355 defer st.Close() 356 f := factory.NewFactory(st, s.StatePool) 357 ch := f.MakeCharm(c, &factory.CharmParams{Name: "elastic-operator", Series: "kubernetes"}) 358 app := f.MakeApplication(c, &factory.ApplicationParams{Name: "elastic-operator", Charm: ch}) 359 360 // Create a charm with new deployment info in metadata. 361 metaYaml := ` 362 name: elastic-operator 363 summary: test 364 description: test 365 provides: 366 website: 367 interface: http 368 requires: 369 db: 370 interface: mysql 371 series: 372 - kubernetes 373 deployment: 374 type: stateful 375 service: loadbalancer 376 `[1:] 377 newCh := state.AddCustomCharm(c, st, "elastic-operator", "metadata.yaml", metaYaml, "kubernetes", 2) 378 cfg := state.SetCharmConfig{ 379 Charm: newCh, 380 CharmOrigin: defaultCharmOrigin(newCh.URL()), 381 ForceUnits: true, 382 } 383 err := app.SetCharm(cfg) 384 c.Assert(err, gc.ErrorMatches, `cannot upgrade application "elastic-operator" to charm "local:kubernetes/kubernetes-elastic-operator-2": cannot change a charm's deployment type`) 385 } 386 387 func (s *ApplicationSuite) TestCAASSetCharmNewDeploymentModeFails(c *gc.C) { 388 st := s.Factory.MakeModel(c, &factory.ModelParams{ 389 Name: "caas-model", 390 Type: state.ModelTypeCAAS, 391 }) 392 defer st.Close() 393 f := factory.NewFactory(st, s.StatePool) 394 ch := f.MakeCharm(c, &factory.CharmParams{Name: "elastic-operator", Series: "kubernetes"}) 395 app := f.MakeApplication(c, &factory.ApplicationParams{Name: "elastic-operator", Charm: ch}) 396 397 // Create a charm with new deployment info in metadata. 398 metaYaml := ` 399 name: elastic-operator 400 summary: test 401 description: test 402 provides: 403 website: 404 interface: http 405 requires: 406 db: 407 interface: mysql 408 series: 409 - kubernetes 410 deployment: 411 mode: workload 412 `[1:] 413 newCh := state.AddCustomCharm(c, st, "elastic-operator", "metadata.yaml", metaYaml, "kubernetes", 2) 414 cfg := state.SetCharmConfig{ 415 Charm: newCh, 416 CharmOrigin: defaultCharmOrigin(newCh.URL()), 417 ForceUnits: true, 418 } 419 err := app.SetCharm(cfg) 420 c.Assert(err, gc.ErrorMatches, `cannot upgrade application "elastic-operator" to charm "local:kubernetes/kubernetes-elastic-operator-2": cannot change a charm's deployment mode`) 421 } 422 423 func (s *ApplicationSuite) TestSetCharmWithNewBindings(c *gc.C) { 424 sp := s.assignUnitOnMachineWithSpaceToApplication(c, s.mysql, "isolated") 425 sch := s.AddMetaCharm(c, "mysql", metaBaseWithNewEndpoint, 2) 426 427 // Assign new charm endpoint to "isolated" space 428 cfg := state.SetCharmConfig{ 429 Charm: sch, 430 CharmOrigin: defaultCharmOrigin(sch.URL()), 431 ForceUnits: true, 432 EndpointBindings: map[string]string{ 433 "events": sp.Name(), 434 }, 435 } 436 err := s.mysql.SetCharm(cfg) 437 c.Assert(err, jc.ErrorIsNil) 438 439 expBindings := map[string]string{ 440 "": network.AlphaSpaceId, 441 "server": network.AlphaSpaceId, 442 "client": network.AlphaSpaceId, 443 "cluster": network.AlphaSpaceId, 444 "events": sp.Id(), 445 } 446 447 updatedBindings, err := s.mysql.EndpointBindings() 448 c.Assert(err, jc.ErrorIsNil) 449 c.Assert(updatedBindings.Map(), gc.DeepEquals, expBindings) 450 } 451 452 func (s *ApplicationSuite) TestMergeBindings(c *gc.C) { 453 s.assignUnitOnMachineWithSpaceToApplication(c, s.mysql, "isolated") 454 455 expBindings := map[string]string{ 456 "": network.AlphaSpaceName, 457 "metrics-client": network.AlphaSpaceName, 458 "server": network.AlphaSpaceName, 459 "server-admin": network.AlphaSpaceName, 460 } 461 b, err := s.mysql.EndpointBindings() 462 c.Assert(err, jc.ErrorIsNil) 463 464 allSpaceInfosLookup, err := s.State.AllSpaceInfos() 465 c.Assert(err, jc.ErrorIsNil) 466 467 curBindings, err := b.MapWithSpaceNames(allSpaceInfosLookup) 468 c.Assert(err, jc.ErrorIsNil) 469 c.Assert(curBindings, gc.DeepEquals, expBindings) 470 471 // Use MergeBindings to bind "server" -> "isolated" 472 b, err = state.NewBindings(s.State, map[string]string{ 473 "server": "isolated", 474 }) 475 c.Assert(err, jc.ErrorIsNil) 476 477 err = s.mysql.MergeBindings(b, false) 478 c.Assert(err, jc.ErrorIsNil) 479 480 // Check that the bindings have been updated 481 expBindings["server"] = "isolated" 482 b, err = s.mysql.EndpointBindings() 483 c.Assert(err, jc.ErrorIsNil) 484 updatedBindings, err := b.MapWithSpaceNames(allSpaceInfosLookup) 485 c.Assert(err, jc.ErrorIsNil) 486 c.Assert(updatedBindings, gc.DeepEquals, expBindings) 487 } 488 489 func (s *ApplicationSuite) TestMergeBindingsWithForce(c *gc.C) { 490 s.assignUnitOnMachineWithSpaceToApplication(c, s.mysql, "isolated") 491 492 sn, err := s.State.AddSubnet(network.SubnetInfo{CIDR: "10.99.99.0/24"}) 493 c.Assert(err, gc.IsNil) 494 _, err = s.State.AddSpace("far", "", []string{sn.ID()}, false) 495 c.Assert(err, gc.IsNil) 496 497 expBindings := map[string]string{ 498 "": network.AlphaSpaceName, 499 "metrics-client": network.AlphaSpaceName, 500 "server": network.AlphaSpaceName, 501 "server-admin": network.AlphaSpaceName, 502 } 503 b, err := s.mysql.EndpointBindings() 504 c.Assert(err, jc.ErrorIsNil) 505 506 allSpaceInfosLookup, err := s.State.AllSpaceInfos() 507 c.Assert(err, jc.ErrorIsNil) 508 509 curBindings, err := b.MapWithSpaceNames(allSpaceInfosLookup) 510 c.Assert(err, jc.ErrorIsNil) 511 c.Assert(curBindings, gc.DeepEquals, expBindings) 512 513 // Use MergeBindings to force-bind "server" -> "far" 514 b, err = state.NewBindings(s.State, map[string]string{ 515 "server": "far", 516 }) 517 c.Assert(err, jc.ErrorIsNil) 518 519 err = s.mysql.MergeBindings(b, true) 520 c.Assert(err, jc.ErrorIsNil) 521 522 // Check that the bindings have been updated 523 expBindings["server"] = "far" 524 b, err = s.mysql.EndpointBindings() 525 c.Assert(err, jc.ErrorIsNil) 526 updatedBindings, err := b.MapWithSpaceNames(allSpaceInfosLookup) 527 c.Assert(err, jc.ErrorIsNil) 528 c.Assert(updatedBindings, gc.DeepEquals, expBindings) 529 } 530 531 func (s *ApplicationSuite) TestSetCharmWithNewBindingsAssigneToDefaultSpace(c *gc.C) { 532 _ = s.assignUnitOnMachineWithSpaceToApplication(c, s.mysql, "isolated") 533 sch := s.AddMetaCharm(c, "mysql", metaBaseWithNewEndpoint, 2) 534 535 // New charm endpoint should be auto-assigned to default space if not 536 // explicitly bound by the operator. 537 cfg := state.SetCharmConfig{ 538 Charm: sch, 539 CharmOrigin: defaultCharmOrigin(sch.URL()), 540 ForceUnits: true, 541 } 542 err := s.mysql.SetCharm(cfg) 543 c.Assert(err, jc.ErrorIsNil) 544 545 expBindings := map[string]string{ 546 "": network.AlphaSpaceId, 547 "server": network.AlphaSpaceId, 548 "client": network.AlphaSpaceId, 549 "cluster": network.AlphaSpaceId, 550 "events": network.AlphaSpaceId, 551 } 552 553 updatedBindings, err := s.mysql.EndpointBindings() 554 c.Assert(err, jc.ErrorIsNil) 555 c.Assert(updatedBindings.Map(), gc.DeepEquals, expBindings) 556 } 557 558 func (s *ApplicationSuite) assignUnitOnMachineWithSpaceToApplication(c *gc.C, a *state.Application, spaceName string) *state.Space { 559 sn1, err := s.State.AddSubnet(network.SubnetInfo{CIDR: "10.0.254.0/24"}) 560 c.Assert(err, gc.IsNil) 561 562 sp, err := s.State.AddSpace(spaceName, "", []string{sn1.ID()}, false) 563 c.Assert(err, gc.IsNil) 564 565 m1, err := s.State.AddOneMachine(state.MachineTemplate{ 566 Base: state.UbuntuBase("12.10"), 567 Jobs: []state.MachineJob{state.JobHostUnits}, 568 Constraints: constraints.MustParse("spaces=isolated"), 569 }) 570 c.Assert(err, gc.IsNil) 571 err = m1.SetLinkLayerDevices(state.LinkLayerDeviceArgs{ 572 Name: "enp5s0", 573 Type: network.EthernetDevice, 574 }) 575 c.Assert(err, gc.IsNil) 576 err = m1.SetDevicesAddresses(state.LinkLayerDeviceAddress{ 577 DeviceName: "enp5s0", 578 CIDRAddress: "10.0.254.42/24", 579 ConfigMethod: network.ConfigStatic, 580 }) 581 c.Assert(err, gc.IsNil) 582 583 u1, err := a.AddUnit(state.AddUnitParams{}) 584 c.Assert(err, gc.IsNil) 585 err = u1.AssignToMachine(m1) 586 c.Assert(err, gc.IsNil) 587 588 return sp 589 } 590 591 func (s *ApplicationSuite) combinedSettings(ch *state.Charm, inSettings charm.Settings) charm.Settings { 592 result := ch.Config().DefaultSettings() 593 for name, value := range inSettings { 594 result[name] = value 595 } 596 return result 597 } 598 599 func (s *ApplicationSuite) TestSetCharmCharmSettings(c *gc.C) { 600 newCh := s.AddConfigCharm(c, "mysql", stringConfig, 2) 601 err := s.mysql.SetCharm(state.SetCharmConfig{ 602 Charm: newCh, 603 CharmOrigin: defaultCharmOrigin(newCh.URL()), 604 ConfigSettings: charm.Settings{"key": "value"}, 605 }) 606 c.Assert(err, jc.ErrorIsNil) 607 608 cfg, err := s.mysql.CharmConfig(model.GenerationMaster) 609 c.Assert(err, jc.ErrorIsNil) 610 c.Assert(cfg, jc.DeepEquals, s.combinedSettings(newCh, charm.Settings{"key": "value"})) 611 612 newCh = s.AddConfigCharm(c, "mysql", newStringConfig, 3) 613 err = s.mysql.SetCharm(state.SetCharmConfig{ 614 Charm: newCh, 615 CharmOrigin: defaultCharmOrigin(newCh.URL()), 616 ConfigSettings: charm.Settings{"other": "one"}, 617 }) 618 c.Assert(err, jc.ErrorIsNil) 619 620 cfg, err = s.mysql.CharmConfig(model.GenerationMaster) 621 c.Assert(err, jc.ErrorIsNil) 622 c.Assert(cfg, jc.DeepEquals, s.combinedSettings(newCh, charm.Settings{ 623 "key": "value", 624 "other": "one", 625 })) 626 } 627 628 func (s *ApplicationSuite) TestSetCharmCharmSettingsForBranch(c *gc.C) { 629 c.Assert(s.State.AddBranch("new-branch", "branch-user"), jc.ErrorIsNil) 630 631 newCh := s.AddConfigCharm(c, "mysql", stringConfig, 2) 632 err := s.mysql.SetCharm(state.SetCharmConfig{ 633 Charm: newCh, 634 CharmOrigin: defaultCharmOrigin(newCh.URL()), 635 ConfigSettings: charm.Settings{"key": "value"}, 636 }) 637 c.Assert(err, jc.ErrorIsNil) 638 639 cfg, err := s.mysql.CharmConfig(model.GenerationMaster) 640 c.Assert(err, jc.ErrorIsNil) 641 642 // Update the next generation settings. 643 cfg["key"] = "next-gen-value" 644 c.Assert(s.mysql.UpdateCharmConfig("new-branch", cfg), jc.ErrorIsNil) 645 646 // Settings for the next generation reflect the change. 647 cfg, err = s.mysql.CharmConfig("new-branch") 648 c.Assert(err, jc.ErrorIsNil) 649 c.Assert(cfg, jc.DeepEquals, s.combinedSettings(newCh, charm.Settings{ 650 "key": "next-gen-value", 651 })) 652 653 // Settings for the current generation are as set with charm. 654 cfg, err = s.mysql.CharmConfig(model.GenerationMaster) 655 c.Assert(err, jc.ErrorIsNil) 656 c.Assert(cfg, jc.DeepEquals, s.combinedSettings(newCh, charm.Settings{ 657 "key": "value", 658 })) 659 } 660 661 func (s *ApplicationSuite) TestSetCharmCharmSettingsInvalid(c *gc.C) { 662 newCh := s.AddConfigCharm(c, "mysql", stringConfig, 2) 663 err := s.mysql.SetCharm(state.SetCharmConfig{ 664 Charm: newCh, 665 CharmOrigin: defaultCharmOrigin(newCh.URL()), 666 ConfigSettings: charm.Settings{"key": 123.45}, 667 }) 668 c.Assert(err, gc.ErrorMatches, `cannot upgrade application "mysql" to charm "local:quantal/quantal-mysql-2": validating config settings: option "key" expected string, got 123.45`) 669 } 670 671 func (s *ApplicationSuite) TestClientApplicationSetCharmUnsupportedSeries(c *gc.C) { 672 ch := state.AddTestingCharmMultiSeries(c, s.State, "multi-series") 673 app := state.AddTestingApplicationForBase(c, s.State, state.UbuntuBase("12.04"), "application", ch) 674 675 chDifferentSeries := state.AddTestingCharmMultiSeries(c, s.State, "multi-series2") 676 cfg := state.SetCharmConfig{ 677 Charm: chDifferentSeries, 678 CharmOrigin: defaultCharmOrigin(chDifferentSeries.URL()), 679 } 680 err := app.SetCharm(cfg) 681 c.Assert(err, gc.ErrorMatches, `cannot upgrade application "application" to charm "ch:multi-series2-8": base "ubuntu@12.04" not supported by charm, the charm supported bases are: ubuntu@14.04, ubuntu@15.10`) 682 } 683 684 func (s *ApplicationSuite) TestClientApplicationSetCharmUnsupportedSeriesForce(c *gc.C) { 685 ch := state.AddTestingCharmMultiSeries(c, s.State, "multi-series") 686 app := state.AddTestingApplicationForBase(c, s.State, state.UbuntuBase("12.04"), "application", ch) 687 688 chDifferentSeries := state.AddTestingCharmMultiSeries(c, s.State, "multi-series2") 689 cfg := state.SetCharmConfig{ 690 Charm: chDifferentSeries, 691 CharmOrigin: defaultCharmOrigin(chDifferentSeries.URL()), 692 ForceBase: true, 693 } 694 err := app.SetCharm(cfg) 695 c.Assert(err, jc.ErrorIsNil) 696 app, err = s.State.Application("application") 697 c.Assert(err, jc.ErrorIsNil) 698 ch, _, err = app.Charm() 699 c.Assert(err, jc.ErrorIsNil) 700 c.Assert(ch.URL(), gc.Equals, "ch:multi-series2-8") 701 } 702 703 func (s *ApplicationSuite) TestClientApplicationSetCharmWrongOS(c *gc.C) { 704 ch := state.AddTestingCharmMultiSeries(c, s.State, "multi-series") 705 app := state.AddTestingApplicationForBase(c, s.State, state.UbuntuBase("12.04"), "application", ch) 706 707 chDifferentSeries := state.AddTestingCharmMultiSeries(c, s.State, "multi-series-centos") 708 cfg := state.SetCharmConfig{ 709 Charm: chDifferentSeries, 710 CharmOrigin: defaultCharmOrigin(chDifferentSeries.URL()), 711 ForceBase: true, 712 } 713 err := app.SetCharm(cfg) 714 c.Assert(err, gc.ErrorMatches, `cannot upgrade application "application" to charm "ch:multi-series-centos-1": OS "ubuntu" not supported by charm.*`) 715 } 716 717 func (s *ApplicationSuite) TestSetCharmPreconditions(c *gc.C) { 718 logging := s.AddTestingCharm(c, "logging") 719 cfg := state.SetCharmConfig{ 720 Charm: logging, 721 CharmOrigin: defaultCharmOrigin(logging.URL()), 722 } 723 err := s.mysql.SetCharm(cfg) 724 c.Assert(err, gc.ErrorMatches, `cannot upgrade application "mysql" to charm "local:quantal/quantal-logging-1": cannot change an application's subordinacy`) 725 } 726 727 func (s *ApplicationSuite) TestSetCharmUpdatesBindings(c *gc.C) { 728 dbSpace, err := s.State.AddSpace("db", "", nil, false) 729 c.Assert(err, jc.ErrorIsNil) 730 clientSpace, err := s.State.AddSpace("client", "", nil, true) 731 c.Assert(err, jc.ErrorIsNil) 732 oldCharm := s.AddMetaCharm(c, "mysql", metaBase, 44) 733 734 application, err := s.State.AddApplication(state.AddApplicationArgs{ 735 Name: "yoursql", 736 Charm: oldCharm, 737 CharmOrigin: &state.CharmOrigin{Platform: &state.Platform{ 738 OS: "ubuntu", 739 Channel: "12.10/stable", 740 }}, 741 EndpointBindings: map[string]string{ 742 "": dbSpace.Id(), 743 "server": dbSpace.Id(), 744 "client": clientSpace.Id(), 745 }}) 746 c.Assert(err, jc.ErrorIsNil) 747 748 newCharm := s.AddMetaCharm(c, "mysql", metaExtraEndpoints, 43) 749 cfg := state.SetCharmConfig{ 750 Charm: newCharm, 751 CharmOrigin: defaultCharmOrigin(newCharm.URL()), 752 } 753 err = application.SetCharm(cfg) 754 c.Assert(err, jc.ErrorIsNil) 755 updatedBindings, err := application.EndpointBindings() 756 c.Assert(err, jc.ErrorIsNil) 757 c.Assert(updatedBindings.Map(), jc.DeepEquals, map[string]string{ 758 // Existing bindings are preserved. 759 "": dbSpace.Id(), 760 "server": dbSpace.Id(), 761 "client": clientSpace.Id(), 762 "cluster": dbSpace.Id(), // inherited from defaults in AddApplication. 763 // New endpoints use defaults. 764 "foo": dbSpace.Id(), 765 "baz": dbSpace.Id(), 766 "just": dbSpace.Id(), 767 }) 768 } 769 770 var metaRelationConsumer = ` 771 name: sqlvampire 772 summary: "connects to an sql server" 773 description: "lorem ipsum" 774 requires: 775 server: mysql 776 ` 777 var metaBase = ` 778 name: mysql 779 summary: "Fake MySQL Database engine" 780 description: "Complete with nonsense relations" 781 provides: 782 server: mysql 783 requires: 784 client: mysql 785 peers: 786 cluster: mysql 787 ` 788 var metaBaseCAAS = ` 789 name: mysql 790 summary: "Fake MySQL Database engine" 791 description: "Complete with nonsense relations" 792 provides: 793 server: mysql 794 requires: 795 client: mysql 796 peers: 797 cluster: mysql 798 ` 799 var metaBaseWithNewEndpoint = ` 800 name: mysql 801 summary: "Fake MySQL Database engine" 802 description: "Complete with nonsense relations" 803 provides: 804 server: mysql 805 requires: 806 client: mysql 807 peers: 808 cluster: mysql 809 extra-bindings: 810 events: 811 ` 812 var metaDifferentProvider = ` 813 name: mysql 814 description: none 815 summary: none 816 provides: 817 server: mysql 818 kludge: mysql 819 requires: 820 client: mysql 821 peers: 822 cluster: mysql 823 ` 824 var metaDifferentRequirer = ` 825 name: mysql 826 description: none 827 summary: none 828 provides: 829 server: mysql 830 requires: 831 kludge: mysql 832 peers: 833 cluster: mysql 834 ` 835 var metaDifferentPeer = ` 836 name: mysql 837 description: none 838 summary: none 839 provides: 840 server: mysql 841 requires: 842 client: mysql 843 peers: 844 kludge: mysql 845 ` 846 var metaRemoveNonPeerRelation = ` 847 name: mysql 848 summary: "Fake MySQL Database engine" 849 description: "Complete with nonsense relations" 850 requires: 851 client: mysql 852 peers: 853 cluster: mysql 854 ` 855 var metaExtraEndpoints = ` 856 name: mysql 857 description: none 858 summary: none 859 provides: 860 server: mysql 861 foo: bar 862 requires: 863 client: mysql 864 baz: woot 865 peers: 866 cluster: mysql 867 just: me 868 ` 869 var lxdProfileMetaBase = ` 870 name: lxd-profile 871 summary: "Fake LXDProfile" 872 description: "Fake description" 873 ` 874 875 var setCharmEndpointsTests = []struct { 876 summary string 877 meta string 878 err string 879 }{{ 880 summary: "different provider (but no relation yet)", 881 meta: metaDifferentProvider, 882 }, { 883 summary: "different requirer (but no relation yet)", 884 meta: metaDifferentRequirer, 885 }, { 886 summary: "different peer", 887 meta: metaDifferentPeer, 888 }, { 889 summary: "attempt to break existing non-peer relations", 890 meta: metaRemoveNonPeerRelation, 891 err: `.*would break relation "fakeother:server fakemysql:server"`, 892 }, { 893 summary: "same relations ok", 894 meta: metaBase, 895 }, { 896 summary: "extra endpoints ok", 897 meta: metaExtraEndpoints, 898 }} 899 900 func (s *ApplicationSuite) TestSetCharmChecksEndpointsWithoutRelations(c *gc.C) { 901 revno := 2 902 ms := s.AddMetaCharm(c, "mysql", metaBase, revno) 903 app := s.AddTestingApplication(c, "fakemysql", ms) 904 appServerEP, err := app.Endpoint("server") 905 c.Assert(err, jc.ErrorIsNil) 906 907 otherCharm := s.AddMetaCharm(c, "dummy", metaRelationConsumer, 42) 908 otherApp := s.AddTestingApplication(c, "fakeother", otherCharm) 909 otherServerEP, err := otherApp.Endpoint("server") 910 c.Assert(err, jc.ErrorIsNil) 911 912 // Add two mysql units so that peer relations get established and we 913 // can check that we are allowed to break them when we upgrade. 914 _, err = app.AddUnit(state.AddUnitParams{}) 915 c.Assert(err, jc.ErrorIsNil) 916 _, err = app.AddUnit(state.AddUnitParams{}) 917 c.Assert(err, jc.ErrorIsNil) 918 919 // Add a unit for the other application and establish a relation. 920 _, err = otherApp.AddUnit(state.AddUnitParams{}) 921 c.Assert(err, jc.ErrorIsNil) 922 _, err = s.State.AddRelation(appServerEP, otherServerEP) 923 c.Assert(err, jc.ErrorIsNil) 924 925 cfg := state.SetCharmConfig{ 926 Charm: ms, 927 CharmOrigin: defaultCharmOrigin(ms.URL()), 928 } 929 err = app.SetCharm(cfg) 930 c.Assert(err, jc.ErrorIsNil) 931 932 for i, t := range setCharmEndpointsTests { 933 c.Logf("test %d: %s", i, t.summary) 934 935 newCh := s.AddMetaCharm(c, "mysql", t.meta, revno+i+1) 936 cfg := state.SetCharmConfig{ 937 Charm: newCh, 938 CharmOrigin: defaultCharmOrigin(newCh.URL()), 939 } 940 err = app.SetCharm(cfg) 941 if t.err != "" { 942 c.Assert(err, gc.ErrorMatches, t.err) 943 } else { 944 c.Assert(err, jc.ErrorIsNil) 945 } 946 } 947 948 err = app.Destroy() 949 c.Assert(err, jc.ErrorIsNil) 950 } 951 952 func (s *ApplicationSuite) TestSetCharmChecksEndpointsWithRelations(c *gc.C) { 953 revno := 2 954 providerCharm := s.AddMetaCharm(c, "mysql", metaDifferentProvider, revno) 955 providerApp := s.AddTestingApplication(c, "myprovider", providerCharm) 956 957 cfg := state.SetCharmConfig{ 958 Charm: providerCharm, 959 CharmOrigin: defaultCharmOrigin(providerCharm.URL()), 960 } 961 err := providerApp.SetCharm(cfg) 962 c.Assert(err, jc.ErrorIsNil) 963 964 revno++ 965 requirerCharm := s.AddMetaCharm(c, "mysql", metaDifferentRequirer, revno) 966 requirerApp := s.AddTestingApplication(c, "myrequirer", requirerCharm) 967 cfg = state.SetCharmConfig{Charm: requirerCharm, CharmOrigin: defaultCharmOrigin(requirerCharm.URL())} 968 err = requirerApp.SetCharm(cfg) 969 c.Assert(err, jc.ErrorIsNil) 970 971 eps, err := s.State.InferEndpoints("myprovider:kludge", "myrequirer:kludge") 972 c.Assert(err, jc.ErrorIsNil) 973 _, err = s.State.AddRelation(eps...) 974 c.Assert(err, jc.ErrorIsNil) 975 976 revno++ 977 baseCharm := s.AddMetaCharm(c, "mysql", metaBase, revno) 978 cfg = state.SetCharmConfig{ 979 Charm: baseCharm, 980 CharmOrigin: defaultCharmOrigin(baseCharm.URL()), 981 } 982 err = providerApp.SetCharm(cfg) 983 c.Assert(err, gc.ErrorMatches, `cannot upgrade application "myprovider" to charm "local:quantal/quantal-mysql-4": would break relation "myrequirer:kludge myprovider:kludge"`) 984 err = requirerApp.SetCharm(cfg) 985 c.Assert(err, gc.ErrorMatches, `cannot upgrade application "myrequirer" to charm "local:quantal/quantal-mysql-4": would break relation "myrequirer:kludge myprovider:kludge"`) 986 } 987 988 var stringConfig = ` 989 options: 990 key: {default: My Key, description: Desc, type: string} 991 ` 992 var emptyConfig = ` 993 options: {} 994 ` 995 var floatConfig = ` 996 options: 997 key: {default: 0.42, description: Float key, type: float} 998 ` 999 var newStringConfig = ` 1000 options: 1001 key: {default: My Key, description: Desc, type: string} 1002 other: {default: None, description: My Other, type: string} 1003 ` 1004 1005 var sortableConfig = ` 1006 options: 1007 blog-title: {default: My Title, description: A descriptive title used for the blog., type: string} 1008 alphabetic: 1009 type: int 1010 description: Something early in the alphabet. 1011 zygomatic: 1012 type: int 1013 description: Something late in the alphabet. 1014 ` 1015 1016 var wordpressConfig = ` 1017 options: 1018 blog-title: {default: My Title, description: A descriptive title used for the blog., type: string} 1019 ` 1020 1021 var setCharmConfigTests = []struct { 1022 summary string 1023 startconfig string 1024 startvalues charm.Settings 1025 endconfig string 1026 endvalues charm.Settings 1027 err string 1028 }{{ 1029 summary: "add float key to empty config", 1030 startconfig: emptyConfig, 1031 endconfig: floatConfig, 1032 }, { 1033 summary: "add string key to empty config", 1034 startconfig: emptyConfig, 1035 endconfig: stringConfig, 1036 }, { 1037 summary: "add string key and preserve existing values", 1038 startconfig: stringConfig, 1039 startvalues: charm.Settings{"key": "foo"}, 1040 endconfig: newStringConfig, 1041 endvalues: charm.Settings{"key": "foo"}, 1042 }, { 1043 summary: "remove string key", 1044 startconfig: stringConfig, 1045 startvalues: charm.Settings{"key": "value"}, 1046 endconfig: emptyConfig, 1047 }, { 1048 summary: "remove float key", 1049 startconfig: floatConfig, 1050 startvalues: charm.Settings{"key": 123.45}, 1051 endconfig: emptyConfig, 1052 }, { 1053 summary: "change key type without values", 1054 startconfig: stringConfig, 1055 endconfig: floatConfig, 1056 }, { 1057 summary: "change key type with values", 1058 startconfig: stringConfig, 1059 startvalues: charm.Settings{"key": "value"}, 1060 endconfig: floatConfig, 1061 }} 1062 1063 func (s *ApplicationSuite) TestSetCharmConfig(c *gc.C) { 1064 charms := map[string]*state.Charm{ 1065 stringConfig: s.AddConfigCharm(c, "wordpress", stringConfig, 1), 1066 emptyConfig: s.AddConfigCharm(c, "wordpress", emptyConfig, 2), 1067 floatConfig: s.AddConfigCharm(c, "wordpress", floatConfig, 3), 1068 newStringConfig: s.AddConfigCharm(c, "wordpress", newStringConfig, 4), 1069 } 1070 1071 for i, t := range setCharmConfigTests { 1072 c.Logf("test %d: %s", i, t.summary) 1073 1074 origCh := charms[t.startconfig] 1075 app := s.AddTestingApplication(c, "wordpress", origCh) 1076 err := app.UpdateCharmConfig(model.GenerationMaster, t.startvalues) 1077 c.Assert(err, jc.ErrorIsNil) 1078 1079 newCh := charms[t.endconfig] 1080 cfg := state.SetCharmConfig{ 1081 Charm: newCh, 1082 CharmOrigin: defaultCharmOrigin(newCh.URL()), 1083 } 1084 err = app.SetCharm(cfg) 1085 var expectVals charm.Settings 1086 var expectCh *state.Charm 1087 if t.err != "" { 1088 c.Assert(err, gc.ErrorMatches, t.err) 1089 expectCh = origCh 1090 expectVals = t.startvalues 1091 } else { 1092 c.Assert(err, jc.ErrorIsNil) 1093 expectCh = newCh 1094 expectVals = t.endvalues 1095 } 1096 1097 sch, _, err := app.Charm() 1098 c.Assert(err, jc.ErrorIsNil) 1099 c.Assert(sch.URL(), gc.DeepEquals, expectCh.URL()) 1100 1101 chConfig, err := app.CharmConfig(model.GenerationMaster) 1102 c.Assert(err, jc.ErrorIsNil) 1103 expected := s.combinedSettings(sch, expectVals) 1104 if len(expected) == 0 { 1105 c.Assert(chConfig, gc.HasLen, 0) 1106 } else { 1107 c.Assert(chConfig, gc.DeepEquals, expected) 1108 } 1109 1110 err = app.Destroy() 1111 c.Assert(err, jc.ErrorIsNil) 1112 } 1113 } 1114 1115 func (s *ApplicationSuite) TestSetCharmWithDyingApplication(c *gc.C) { 1116 sch := s.AddMetaCharm(c, "mysql", metaBase, 2) 1117 1118 _, err := s.mysql.AddUnit(state.AddUnitParams{}) 1119 c.Assert(err, jc.ErrorIsNil) 1120 err = s.mysql.Destroy() 1121 c.Assert(err, jc.ErrorIsNil) 1122 assertLife(c, s.mysql, state.Dying) 1123 cfg := state.SetCharmConfig{ 1124 Charm: sch, 1125 CharmOrigin: defaultCharmOrigin(sch.URL()), 1126 ForceUnits: true, 1127 } 1128 err = s.mysql.SetCharm(cfg) 1129 c.Assert(err, jc.ErrorIsNil) 1130 } 1131 1132 func (s *ApplicationSuite) TestSequenceUnitIdsAfterDestroy(c *gc.C) { 1133 unit, err := s.mysql.AddUnit(state.AddUnitParams{}) 1134 c.Assert(err, jc.ErrorIsNil) 1135 c.Assert(unit.Name(), gc.Equals, "mysql/0") 1136 err = unit.Destroy() 1137 c.Assert(err, jc.ErrorIsNil) 1138 err = s.mysql.Destroy() 1139 c.Assert(err, jc.ErrorIsNil) 1140 assertRemoved(c, s.mysql) 1141 s.mysql = s.AddTestingApplication(c, "mysql", s.charm) 1142 unit, err = s.mysql.AddUnit(state.AddUnitParams{}) 1143 c.Assert(err, jc.ErrorIsNil) 1144 c.Assert(unit.Name(), gc.Equals, "mysql/1") 1145 } 1146 1147 func (s *ApplicationSuite) TestAssignUnitsRemovedAfterAppDestroy(c *gc.C) { 1148 mariadb := s.AddTestingApplicationWithNumUnits(c, 1, "mariadb", s.charm) 1149 s.WaitForModelWatchersIdle(c, s.Model.UUID()) 1150 1151 units, err := mariadb.AllUnits() 1152 c.Assert(err, jc.ErrorIsNil) 1153 c.Assert(len(units), gc.Equals, 1) 1154 unit := units[0] 1155 c.Assert(unit.Name(), gc.Equals, "mariadb/0") 1156 unitAssignments, err := s.State.AllUnitAssignments() 1157 c.Assert(err, jc.ErrorIsNil) 1158 c.Assert(len(unitAssignments), gc.Equals, 1) 1159 1160 err = unit.Destroy() 1161 c.Assert(err, jc.ErrorIsNil) 1162 err = mariadb.Destroy() 1163 c.Assert(err, jc.ErrorIsNil) 1164 assertRemoved(c, mariadb) 1165 1166 unitAssignments, err = s.State.AllUnitAssignments() 1167 c.Assert(err, jc.ErrorIsNil) 1168 c.Assert(len(unitAssignments), gc.Equals, 0) 1169 } 1170 1171 func (s *ApplicationSuite) TestSequenceUnitIdsAfterDestroyForSidecarApplication(c *gc.C) { 1172 st := s.Factory.MakeModel(c, &factory.ModelParams{ 1173 Name: "caas-model", 1174 Type: state.ModelTypeCAAS, 1175 }) 1176 s.AddCleanup(func(*gc.C) { _ = st.Close() }) 1177 f := factory.NewFactory(st, s.StatePool) 1178 charmDef := ` 1179 name: cockroachdb 1180 description: foo 1181 summary: foo 1182 containers: 1183 redis: 1184 resource: redis-container-resource 1185 resources: 1186 redis-container-resource: 1187 name: redis-container 1188 type: oci-image 1189 ` 1190 ch := state.AddCustomCharmWithManifest(c, st, "cockroach", "metadata.yaml", charmDef, "focal", 1) 1191 app := f.MakeApplication(c, &factory.ApplicationParams{Name: "cockroachdb", Charm: ch}) 1192 unit, err := app.AddUnit(state.AddUnitParams{}) 1193 c.Assert(err, jc.ErrorIsNil) 1194 c.Assert(unit.Name(), gc.Equals, "cockroachdb/0") 1195 err = unit.Destroy() 1196 c.Assert(err, jc.ErrorIsNil) 1197 err = unit.Refresh() 1198 c.Assert(err, jc.Satisfies, errors.IsNotFound) 1199 err = app.Destroy() 1200 c.Assert(err, jc.ErrorIsNil) 1201 err = app.ClearResources() 1202 c.Assert(err, jc.ErrorIsNil) 1203 s.WaitForModelWatchersIdle(c, st.ModelUUID()) 1204 assertCleanupCount(c, st, 2) 1205 unitAssignments, err := st.AllUnitAssignments() 1206 c.Assert(err, jc.ErrorIsNil) 1207 c.Assert(len(unitAssignments), gc.Equals, 0) 1208 1209 ch = state.AddCustomCharmWithManifest(c, st, "cockroach", "metadata.yaml", charmDef, "focal", 1) 1210 app = f.MakeApplication(c, &factory.ApplicationParams{Name: "cockroachdb", Charm: ch}) 1211 unit, err = app.AddUnit(state.AddUnitParams{}) 1212 c.Assert(err, jc.ErrorIsNil) 1213 c.Assert(unit.Name(), gc.Equals, "cockroachdb/0") 1214 } 1215 1216 func (s *ApplicationSuite) TestSequenceUnitIds(c *gc.C) { 1217 unit, err := s.mysql.AddUnit(state.AddUnitParams{}) 1218 c.Assert(err, jc.ErrorIsNil) 1219 c.Assert(unit.Name(), gc.Equals, "mysql/0") 1220 unit, err = s.mysql.AddUnit(state.AddUnitParams{}) 1221 c.Assert(err, jc.ErrorIsNil) 1222 c.Assert(unit.Name(), gc.Equals, "mysql/1") 1223 } 1224 1225 func (s *ApplicationSuite) TestExplicitUnitName(c *gc.C) { 1226 name1 := "mysql/100" 1227 unit, err := s.mysql.AddUnit(state.AddUnitParams{ 1228 UnitName: &name1, 1229 }) 1230 c.Assert(err, jc.ErrorIsNil) 1231 c.Assert(unit.Name(), gc.Equals, name1) 1232 name0 := "mysql/0" 1233 unit, err = s.mysql.AddUnit(state.AddUnitParams{ 1234 UnitName: &name0, 1235 }) 1236 c.Assert(err, jc.ErrorIsNil) 1237 c.Assert(unit.Name(), gc.Equals, name0) 1238 } 1239 1240 func (s *ApplicationSuite) TestSetCharmWhenDead(c *gc.C) { 1241 sch := s.AddMetaCharm(c, "mysql", metaBase, 2) 1242 1243 defer state.SetBeforeHooks(c, s.State, func() { 1244 _, err := s.mysql.AddUnit(state.AddUnitParams{}) 1245 c.Assert(err, jc.ErrorIsNil) 1246 err = s.mysql.Destroy() 1247 c.Assert(err, jc.ErrorIsNil) 1248 assertLife(c, s.mysql, state.Dying) 1249 1250 // Change the application life to Dead manually, as there's no 1251 // direct way of doing that otherwise. 1252 ops := []txn.Op{{ 1253 C: state.ApplicationsC, 1254 Id: state.DocID(s.State, s.mysql.Name()), 1255 Update: bson.D{{"$set", bson.D{{"life", state.Dead}}}}, 1256 }} 1257 1258 state.RunTransaction(c, s.State, ops) 1259 assertLife(c, s.mysql, state.Dead) 1260 }).Check() 1261 1262 cfg := state.SetCharmConfig{ 1263 Charm: sch, 1264 CharmOrigin: defaultCharmOrigin(sch.URL()), 1265 ForceUnits: true, 1266 } 1267 err := s.mysql.SetCharm(cfg) 1268 c.Assert(errors.Cause(err), gc.Equals, stateerrors.ErrDead) 1269 } 1270 1271 func (s *ApplicationSuite) TestSetCharmWithRemovedApplication(c *gc.C) { 1272 sch := s.AddMetaCharm(c, "mysql", metaBase, 2) 1273 1274 err := s.mysql.Destroy() 1275 c.Assert(err, jc.ErrorIsNil) 1276 assertRemoved(c, s.mysql) 1277 1278 cfg := state.SetCharmConfig{ 1279 Charm: sch, 1280 CharmOrigin: defaultCharmOrigin(sch.URL()), 1281 ForceUnits: true, 1282 } 1283 1284 err = s.mysql.SetCharm(cfg) 1285 c.Assert(err, jc.Satisfies, errors.IsNotFound) 1286 } 1287 1288 func (s *ApplicationSuite) TestSetCharmWhenRemoved(c *gc.C) { 1289 sch := s.AddMetaCharm(c, "mysql", metaBase, 2) 1290 1291 defer state.SetBeforeHooks(c, s.State, func() { 1292 err := s.mysql.Destroy() 1293 c.Assert(err, jc.ErrorIsNil) 1294 assertRemoved(c, s.mysql) 1295 }).Check() 1296 1297 cfg := state.SetCharmConfig{ 1298 Charm: sch, 1299 CharmOrigin: defaultCharmOrigin(sch.URL()), 1300 ForceUnits: true, 1301 } 1302 err := s.mysql.SetCharm(cfg) 1303 c.Assert(err, jc.Satisfies, errors.IsNotFound) 1304 } 1305 1306 func (s *ApplicationSuite) TestSetCharmWhenDyingIsOK(c *gc.C) { 1307 sch := s.AddMetaCharm(c, "mysql", metaBase, 2) 1308 1309 defer state.SetBeforeHooks(c, s.State, func() { 1310 _, err := s.mysql.AddUnit(state.AddUnitParams{}) 1311 c.Assert(err, jc.ErrorIsNil) 1312 err = s.mysql.Destroy() 1313 c.Assert(err, jc.ErrorIsNil) 1314 assertLife(c, s.mysql, state.Dying) 1315 }).Check() 1316 1317 cfg := state.SetCharmConfig{ 1318 Charm: sch, 1319 CharmOrigin: defaultCharmOrigin(sch.URL()), 1320 ForceUnits: true, 1321 } 1322 err := s.mysql.SetCharm(cfg) 1323 c.Assert(err, jc.ErrorIsNil) 1324 assertLife(c, s.mysql, state.Dying) 1325 } 1326 1327 func (s *ApplicationSuite) TestSetCharmRetriesWithSameCharmURL(c *gc.C) { 1328 sch := s.AddMetaCharm(c, "mysql", metaBase, 2) 1329 1330 defer state.SetTestHooks(c, s.State, 1331 jujutxn.TestHook{ 1332 Before: func() { 1333 currentCh, force, err := s.mysql.Charm() 1334 c.Assert(err, jc.ErrorIsNil) 1335 c.Assert(force, jc.IsFalse) 1336 c.Assert(currentCh.URL(), jc.DeepEquals, s.charm.URL()) 1337 1338 cfg := state.SetCharmConfig{ 1339 Charm: sch, 1340 CharmOrigin: defaultCharmOrigin(sch.URL()), 1341 } 1342 err = s.mysql.SetCharm(cfg) 1343 c.Assert(err, jc.ErrorIsNil) 1344 }, 1345 After: func() { 1346 // Verify the before hook worked. 1347 currentCh, force, err := s.mysql.Charm() 1348 c.Assert(err, jc.ErrorIsNil) 1349 c.Assert(force, jc.IsFalse) 1350 c.Assert(currentCh.URL(), jc.DeepEquals, sch.URL()) 1351 }, 1352 }, 1353 jujutxn.TestHook{ 1354 Before: nil, // Ensure there will be a retry. 1355 After: func() { 1356 // Verify it worked after the retry. 1357 err := s.mysql.Refresh() 1358 c.Assert(err, jc.ErrorIsNil) 1359 currentCh, force, err := s.mysql.Charm() 1360 c.Assert(err, jc.ErrorIsNil) 1361 c.Assert(force, jc.IsTrue) 1362 c.Assert(currentCh.URL(), jc.DeepEquals, sch.URL()) 1363 }, 1364 }, 1365 ).Check() 1366 1367 cfg := state.SetCharmConfig{ 1368 Charm: sch, 1369 CharmOrigin: defaultCharmOrigin(sch.URL()), 1370 ForceUnits: true, 1371 } 1372 err := s.mysql.SetCharm(cfg) 1373 c.Assert(err, jc.ErrorIsNil) 1374 } 1375 1376 func (s *ApplicationSuite) TestSetCharmRetriesWhenOldSettingsChanged(c *gc.C) { 1377 revno := 2 // revno 1 is used by SetUpSuite 1378 oldCh := s.AddConfigCharm(c, "mysql", stringConfig, revno) 1379 newCh := s.AddConfigCharm(c, "mysql", stringConfig, revno+1) 1380 cfg := state.SetCharmConfig{ 1381 Charm: oldCh, 1382 CharmOrigin: defaultCharmOrigin(oldCh.URL()), 1383 } 1384 err := s.mysql.SetCharm(cfg) 1385 c.Assert(err, jc.ErrorIsNil) 1386 1387 defer state.SetBeforeHooks(c, s.State, 1388 func() { 1389 err := s.mysql.UpdateCharmConfig(model.GenerationMaster, charm.Settings{"key": "value"}) 1390 c.Assert(err, jc.ErrorIsNil) 1391 }, 1392 nil, // Ensure there will be a retry. 1393 ).Check() 1394 1395 cfg = state.SetCharmConfig{ 1396 Charm: newCh, 1397 CharmOrigin: defaultCharmOrigin(newCh.URL()), 1398 ForceUnits: true, 1399 } 1400 err = s.mysql.SetCharm(cfg) 1401 c.Assert(err, jc.ErrorIsNil) 1402 } 1403 1404 func (s *ApplicationSuite) TestSetCharmRetriesWhenBothOldAndNewSettingsChanged(c *gc.C) { 1405 revno := 2 // revno 1 is used by SetUpSuite 1406 oldCh := s.AddConfigCharm(c, "mysql", stringConfig, revno) 1407 newCh := s.AddConfigCharm(c, "mysql", stringConfig, revno+1) 1408 1409 defer state.SetTestHooks(c, s.State, 1410 jujutxn.TestHook{ 1411 Before: func() { 1412 // Add two units, which will keep the refcount of oldCh 1413 // and newCh settings greater than 0, while the application's 1414 // charm URLs change between oldCh and newCh. Ensure 1415 // refcounts change as expected. 1416 unit1, err := s.mysql.AddUnit(state.AddUnitParams{}) 1417 c.Assert(err, jc.ErrorIsNil) 1418 unit2, err := s.mysql.AddUnit(state.AddUnitParams{}) 1419 c.Assert(err, jc.ErrorIsNil) 1420 cfg := state.SetCharmConfig{ 1421 Charm: newCh, 1422 CharmOrigin: defaultCharmOrigin(newCh.URL()), 1423 } 1424 err = s.mysql.SetCharm(cfg) 1425 c.Assert(err, jc.ErrorIsNil) 1426 assertSettingsRef(c, s.State, "mysql", newCh, 1) 1427 assertNoSettingsRef(c, s.State, "mysql", oldCh) 1428 err = unit1.SetCharmURL(newCh.URL()) 1429 c.Assert(err, jc.ErrorIsNil) 1430 assertSettingsRef(c, s.State, "mysql", newCh, 2) 1431 assertNoSettingsRef(c, s.State, "mysql", oldCh) 1432 // Update newCh settings, switch to oldCh and update its 1433 // settings as well. 1434 err = s.mysql.UpdateCharmConfig(model.GenerationMaster, charm.Settings{"key": "value1"}) 1435 c.Assert(err, jc.ErrorIsNil) 1436 cfg = state.SetCharmConfig{ 1437 Charm: oldCh, 1438 CharmOrigin: defaultCharmOrigin(oldCh.URL()), 1439 } 1440 1441 err = s.mysql.SetCharm(cfg) 1442 c.Assert(err, jc.ErrorIsNil) 1443 assertSettingsRef(c, s.State, "mysql", newCh, 1) 1444 assertSettingsRef(c, s.State, "mysql", oldCh, 1) 1445 err = unit2.SetCharmURL(oldCh.URL()) 1446 c.Assert(err, jc.ErrorIsNil) 1447 assertSettingsRef(c, s.State, "mysql", newCh, 1) 1448 assertSettingsRef(c, s.State, "mysql", oldCh, 2) 1449 err = s.mysql.UpdateCharmConfig(model.GenerationMaster, charm.Settings{"key": "value2"}) 1450 c.Assert(err, jc.ErrorIsNil) 1451 }, 1452 After: func() { 1453 // Verify the charm and refcounts after the second attempt. 1454 err := s.mysql.Refresh() 1455 c.Assert(err, jc.ErrorIsNil) 1456 currentCh, force, err := s.mysql.Charm() 1457 c.Assert(err, jc.ErrorIsNil) 1458 c.Assert(force, jc.IsFalse) 1459 c.Assert(currentCh.URL(), jc.DeepEquals, oldCh.URL()) 1460 assertSettingsRef(c, s.State, "mysql", newCh, 1) 1461 assertSettingsRef(c, s.State, "mysql", oldCh, 2) 1462 }, 1463 }, 1464 jujutxn.TestHook{ 1465 Before: func() { 1466 // SetCharm has refreshed its cached settings for oldCh 1467 // and newCh. Change them again to trigger another 1468 // attempt. 1469 cfg := state.SetCharmConfig{ 1470 Charm: newCh, 1471 CharmOrigin: defaultCharmOrigin(newCh.URL()), 1472 } 1473 1474 err := s.mysql.SetCharm(cfg) 1475 c.Assert(err, jc.ErrorIsNil) 1476 assertSettingsRef(c, s.State, "mysql", newCh, 2) 1477 assertSettingsRef(c, s.State, "mysql", oldCh, 1) 1478 err = s.mysql.UpdateCharmConfig(model.GenerationMaster, charm.Settings{"key": "value3"}) 1479 c.Assert(err, jc.ErrorIsNil) 1480 1481 cfg = state.SetCharmConfig{ 1482 Charm: oldCh, 1483 CharmOrigin: defaultCharmOrigin(oldCh.URL()), 1484 } 1485 err = s.mysql.SetCharm(cfg) 1486 c.Assert(err, jc.ErrorIsNil) 1487 assertSettingsRef(c, s.State, "mysql", newCh, 1) 1488 assertSettingsRef(c, s.State, "mysql", oldCh, 2) 1489 err = s.mysql.UpdateCharmConfig(model.GenerationMaster, charm.Settings{"key": "value4"}) 1490 c.Assert(err, jc.ErrorIsNil) 1491 }, 1492 After: func() { 1493 // Verify the charm and refcounts after the third attempt. 1494 err := s.mysql.Refresh() 1495 c.Assert(err, jc.ErrorIsNil) 1496 currentCh, force, err := s.mysql.Charm() 1497 c.Assert(err, jc.ErrorIsNil) 1498 c.Assert(force, jc.IsFalse) 1499 c.Assert(currentCh.URL(), jc.DeepEquals, oldCh.URL()) 1500 assertSettingsRef(c, s.State, "mysql", newCh, 1) 1501 assertSettingsRef(c, s.State, "mysql", oldCh, 2) 1502 }, 1503 }, 1504 jujutxn.TestHook{ 1505 Before: nil, // Ensure there will be a (final) retry. 1506 After: func() { 1507 // Verify the charm and refcounts after the final third attempt. 1508 err := s.mysql.Refresh() 1509 c.Assert(err, jc.ErrorIsNil) 1510 currentCh, force, err := s.mysql.Charm() 1511 c.Assert(err, jc.ErrorIsNil) 1512 c.Assert(force, jc.IsTrue) 1513 c.Assert(currentCh.URL(), jc.DeepEquals, newCh.URL()) 1514 assertSettingsRef(c, s.State, "mysql", newCh, 2) 1515 assertSettingsRef(c, s.State, "mysql", oldCh, 1) 1516 }, 1517 }, 1518 ).Check() 1519 1520 cfg := state.SetCharmConfig{ 1521 Charm: newCh, 1522 CharmOrigin: defaultCharmOrigin(newCh.URL()), 1523 ForceUnits: true, 1524 } 1525 err := s.mysql.SetCharm(cfg) 1526 c.Assert(err, jc.ErrorIsNil) 1527 } 1528 1529 func (s *ApplicationSuite) TestSetCharmRetriesWhenOldBindingsChanged(c *gc.C) { 1530 revno := 2 // revno 1 is used by SetUpSuite 1531 mysqlKey := state.ApplicationGlobalKey(s.mysql.Name()) 1532 oldCharm := s.AddMetaCharm(c, "mysql", metaDifferentRequirer, revno) 1533 newCharm := s.AddMetaCharm(c, "mysql", metaExtraEndpoints, revno+1) 1534 1535 cfg := state.SetCharmConfig{ 1536 Charm: oldCharm, 1537 CharmOrigin: defaultCharmOrigin(oldCharm.URL()), 1538 } 1539 err := s.mysql.SetCharm(cfg) 1540 c.Assert(err, jc.ErrorIsNil) 1541 1542 oldBindings, err := s.mysql.EndpointBindings() 1543 c.Assert(err, jc.ErrorIsNil) 1544 c.Assert(oldBindings.Map(), jc.DeepEquals, map[string]string{ 1545 "": network.AlphaSpaceId, 1546 "server": network.AlphaSpaceId, 1547 "kludge": network.AlphaSpaceId, 1548 "cluster": network.AlphaSpaceId, 1549 }) 1550 dbSpace, err := s.State.AddSpace("db", "", nil, true) 1551 c.Assert(err, jc.ErrorIsNil) 1552 adminSpace, err := s.State.AddSpace("admin", "", nil, false) 1553 c.Assert(err, jc.ErrorIsNil) 1554 1555 updateBindings := func(updatesMap bson.M) { 1556 ops := []txn.Op{{ 1557 C: state.EndpointBindingsC, 1558 Id: mysqlKey, 1559 Update: bson.D{{"$set", updatesMap}}, 1560 }} 1561 state.RunTransaction(c, s.State, ops) 1562 } 1563 1564 defer state.SetTestHooks(c, s.State, 1565 jujutxn.TestHook{ 1566 Before: func() { 1567 // First change. 1568 updateBindings(bson.M{ 1569 "bindings.server": dbSpace.Id(), 1570 "bindings.kludge": adminSpace.Id(), // will be removed before newCharm is set. 1571 }) 1572 }, 1573 After: func() { 1574 // Second change. 1575 updateBindings(bson.M{ 1576 "bindings.cluster": adminSpace.Id(), 1577 }) 1578 }, 1579 }, 1580 jujutxn.TestHook{ 1581 Before: nil, // Ensure there will be a (final) retry. 1582 After: func() { 1583 // Verify final bindings. 1584 newBindings, err := s.mysql.EndpointBindings() 1585 c.Assert(err, jc.ErrorIsNil) 1586 c.Assert(newBindings.Map(), jc.DeepEquals, map[string]string{ 1587 "": network.AlphaSpaceId, 1588 "server": dbSpace.Id(), // from the first change. 1589 "foo": network.AlphaSpaceId, 1590 "client": network.AlphaSpaceId, 1591 "baz": network.AlphaSpaceId, 1592 "cluster": adminSpace.Id(), // from the second change. 1593 "just": network.AlphaSpaceId, 1594 }) 1595 }, 1596 }, 1597 ).Check() 1598 1599 cfg = state.SetCharmConfig{ 1600 Charm: newCharm, 1601 CharmOrigin: defaultCharmOrigin(newCharm.URL()), 1602 ForceUnits: true, 1603 } 1604 err = s.mysql.SetCharm(cfg) 1605 c.Assert(err, jc.ErrorIsNil) 1606 } 1607 1608 func (s *ApplicationSuite) TestSetCharmViolatesMaxRelationCount(c *gc.C) { 1609 wp0Charm := ` 1610 name: wordpress 1611 description: foo 1612 summary: foo 1613 requires: 1614 db: 1615 interface: mysql 1616 limit: 99 1617 ` 1618 1619 wp1Charm := ` 1620 name: wordpress 1621 description: bar 1622 summary: foo 1623 requires: 1624 db: 1625 interface: mysql 1626 limit: 1 1627 ` 1628 1629 // wp0Charm allows up to 99 relations for the db endpoint 1630 wpApp := s.AddTestingApplication(c, "wordpress", s.AddMetaCharm(c, "wordpress", wp0Charm, 1)) 1631 1632 // Establish 2 relations (note: mysql is already added by the suite setup code) 1633 s.AddTestingApplication(c, "some-mariadb", s.AddTestingCharm(c, "mariadb")) 1634 eps, err := s.State.InferEndpoints("wordpress", "mysql") 1635 c.Assert(err, jc.ErrorIsNil) 1636 _, err = s.State.AddRelation(eps...) 1637 c.Assert(err, jc.ErrorIsNil) 1638 eps, err = s.State.InferEndpoints("wordpress", "some-mariadb") 1639 c.Assert(err, jc.ErrorIsNil) 1640 _, err = s.State.AddRelation(eps...) 1641 c.Assert(err, jc.ErrorIsNil) 1642 1643 // Try to update wordpress to a new version with max 1 relation for the db endpoint 1644 wpCharmWithRelLimit := s.AddMetaCharm(c, "wordpress", wp1Charm, 2) 1645 cfg := state.SetCharmConfig{ 1646 Charm: wpCharmWithRelLimit, 1647 CharmOrigin: defaultCharmOrigin(wpCharmWithRelLimit.URL()), 1648 } 1649 err = wpApp.SetCharm(cfg) 1650 c.Assert(err, jc.Satisfies, errors.IsQuotaLimitExceeded, gc.Commentf("expected quota limit error due to max relation mismatch")) 1651 1652 // Try again with --force 1653 cfg.Force = true 1654 err = wpApp.SetCharm(cfg) 1655 c.Assert(err, jc.ErrorIsNil) 1656 } 1657 1658 func (s *ApplicationSuite) TestSetDownloadedIDAndHash(c *gc.C) { 1659 s.setupSetDownloadedIDAndHash(c, &state.CharmOrigin{ 1660 Source: "charm-hub", 1661 }) 1662 err := s.mysql.SetDownloadedIDAndHash("testing-ID", "testing-hash") 1663 c.Assert(err, jc.ErrorIsNil) 1664 c.Assert(s.mysql.CharmOrigin().ID, gc.Equals, "testing-ID") 1665 c.Assert(s.mysql.CharmOrigin().Hash, gc.Equals, "testing-hash") 1666 } 1667 1668 func (s *ApplicationSuite) TestSetDownloadedIDAndHashFailEmptyStrings(c *gc.C) { 1669 err := s.mysql.SetDownloadedIDAndHash("", "") 1670 c.Assert(err, jc.Satisfies, errors.IsBadRequest) 1671 } 1672 1673 func (s *ApplicationSuite) TestSetDownloadedIDAndHashFailChangeID(c *gc.C) { 1674 s.setupSetDownloadedIDAndHash(c, &state.CharmOrigin{ 1675 Source: "charm-hub", 1676 ID: "testing-ID", 1677 Hash: "testing-hash", 1678 Platform: &state.Platform{}, 1679 }) 1680 err := s.mysql.SetDownloadedIDAndHash("change-ID", "testing-hash") 1681 c.Assert(err, jc.Satisfies, errors.IsBadRequest) 1682 } 1683 1684 func (s *ApplicationSuite) TestSetDownloadedIDAndHashReplaceHash(c *gc.C) { 1685 s.setupSetDownloadedIDAndHash(c, &state.CharmOrigin{ 1686 Source: "charm-hub", 1687 ID: "testing-ID", 1688 Hash: "testing-hash", 1689 }) 1690 err := s.mysql.SetDownloadedIDAndHash("", "new-testing-hash") 1691 c.Assert(err, jc.ErrorIsNil) 1692 c.Assert(s.mysql.CharmOrigin().Hash, gc.Equals, "new-testing-hash") 1693 } 1694 1695 func (s *ApplicationSuite) setupSetDownloadedIDAndHash(c *gc.C, origin *state.CharmOrigin) { 1696 origin.Platform = &state.Platform{} 1697 chInfoOne := s.dummyCharm(c, "ch:testing-3") 1698 chOne, err := s.State.AddCharm(chInfoOne) 1699 c.Assert(err, jc.ErrorIsNil) 1700 err = s.mysql.SetCharm(state.SetCharmConfig{ 1701 Charm: chOne, 1702 CharmOrigin: origin, 1703 }) 1704 c.Assert(err, jc.ErrorIsNil) 1705 err = s.mysql.Refresh() 1706 c.Assert(err, jc.ErrorIsNil) 1707 } 1708 1709 var applicationUpdateCharmConfigTests = []struct { 1710 about string 1711 initial charm.Settings 1712 update charm.Settings 1713 expect charm.Settings 1714 err string 1715 }{{ 1716 about: "unknown option", 1717 update: charm.Settings{"foo": "bar"}, 1718 err: `unknown option "foo"`, 1719 }, { 1720 about: "bad type", 1721 update: charm.Settings{"skill-level": "profound"}, 1722 err: `option "skill-level" expected int, got "profound"`, 1723 }, { 1724 about: "set string", 1725 update: charm.Settings{"outlook": "positive"}, 1726 expect: charm.Settings{"outlook": "positive"}, 1727 }, { 1728 about: "unset string and set another", 1729 initial: charm.Settings{"outlook": "positive"}, 1730 update: charm.Settings{"outlook": nil, "title": "sir"}, 1731 expect: charm.Settings{"title": "sir"}, 1732 }, { 1733 about: "unset missing string", 1734 update: charm.Settings{"outlook": nil}, 1735 }, { 1736 about: `empty strings are valid`, 1737 initial: charm.Settings{"outlook": "positive"}, 1738 update: charm.Settings{"outlook": "", "title": ""}, 1739 expect: charm.Settings{"outlook": "", "title": ""}, 1740 }, { 1741 about: "preserve existing value", 1742 initial: charm.Settings{"title": "sir"}, 1743 update: charm.Settings{"username": "admin001"}, 1744 expect: charm.Settings{"username": "admin001", "title": "sir"}, 1745 }, { 1746 about: "unset a default value, set a different default", 1747 initial: charm.Settings{"username": "admin001", "title": "sir"}, 1748 update: charm.Settings{"username": nil, "title": "My Title"}, 1749 expect: charm.Settings{"title": "My Title"}, 1750 }, { 1751 about: "non-string type", 1752 update: charm.Settings{"skill-level": 303}, 1753 expect: charm.Settings{"skill-level": int64(303)}, 1754 }, { 1755 about: "unset non-string type", 1756 initial: charm.Settings{"skill-level": 303}, 1757 update: charm.Settings{"skill-level": nil}, 1758 }} 1759 1760 func (s *ApplicationSuite) TestUpdateCharmConfig(c *gc.C) { 1761 sch := s.AddTestingCharm(c, "dummy") 1762 for i, t := range applicationUpdateCharmConfigTests { 1763 c.Logf("test %d. %s", i, t.about) 1764 app := s.AddTestingApplication(c, "dummy-application", sch) 1765 if t.initial != nil { 1766 err := app.UpdateCharmConfig(model.GenerationMaster, t.initial) 1767 c.Assert(err, jc.ErrorIsNil) 1768 } 1769 err := app.UpdateCharmConfig(model.GenerationMaster, t.update) 1770 if t.err != "" { 1771 c.Assert(err, gc.ErrorMatches, t.err) 1772 } else { 1773 c.Assert(err, jc.ErrorIsNil) 1774 cfg, err := app.CharmConfig(model.GenerationMaster) 1775 c.Assert(err, jc.ErrorIsNil) 1776 appConfig := t.expect 1777 expected := s.combinedSettings(sch, appConfig) 1778 c.Assert(cfg, gc.DeepEquals, expected) 1779 } 1780 err = app.Destroy() 1781 c.Assert(err, jc.ErrorIsNil) 1782 } 1783 } 1784 1785 func (s *ApplicationSuite) setupCharmForTestUpdateApplicationBase(c *gc.C, name string) *state.Application { 1786 ch := state.AddTestingCharmMultiSeries(c, s.State, name) 1787 app := state.AddTestingApplicationForBase(c, s.State, state.UbuntuBase("20.04"), name, ch) 1788 1789 rev := ch.Revision() 1790 origin := &state.CharmOrigin{ 1791 Source: "charm-hub", 1792 Revision: &rev, 1793 Platform: &state.Platform{ 1794 OS: "ubuntu", 1795 Channel: "20.04/stable", 1796 }, 1797 } 1798 cfg := state.SetCharmConfig{ 1799 Charm: ch, 1800 CharmOrigin: origin, 1801 } 1802 err := app.SetCharm(cfg) 1803 c.Assert(err, jc.ErrorIsNil) 1804 err = app.Refresh() 1805 c.Assert(err, jc.ErrorIsNil) 1806 return app 1807 } 1808 1809 func (s *ApplicationSuite) TestUpdateApplicationBase(c *gc.C) { 1810 app := s.setupCharmForTestUpdateApplicationBase(c, "multi-series") 1811 err := app.UpdateApplicationBase(state.UbuntuBase("22.04"), false) 1812 c.Assert(err, jc.ErrorIsNil) 1813 assertApplicationBaseUpdate(c, app, state.UbuntuBase("22.04")) 1814 } 1815 1816 func (s *ApplicationSuite) TestUpdateApplicationSeriesSamesSeriesToStart(c *gc.C) { 1817 app := s.setupCharmForTestUpdateApplicationBase(c, "multi-series") 1818 err := app.UpdateApplicationBase(state.UbuntuBase("20.04"), false) 1819 c.Assert(err, jc.ErrorIsNil) 1820 assertApplicationBaseUpdate(c, app, state.UbuntuBase("20.04")) 1821 } 1822 1823 func (s *ApplicationSuite) TestUpdateApplicationSeriesSamesSeriesAfterStart(c *gc.C) { 1824 app := s.setupCharmForTestUpdateApplicationBase(c, "multi-series") 1825 1826 defer state.SetTestHooks(c, s.State, 1827 jujutxn.TestHook{ 1828 Before: func() { 1829 unit, err := app.AddUnit(state.AddUnitParams{}) 1830 c.Assert(err, jc.ErrorIsNil) 1831 err = unit.AssignToNewMachine() 1832 c.Assert(err, jc.ErrorIsNil) 1833 1834 ops := []txn.Op{{ 1835 C: state.ApplicationsC, 1836 Id: state.DocID(s.State, "multi-series"), 1837 Update: bson.D{{"$set", bson.D{{ 1838 "charm-origin.platform.channel", "22.04/stable"}}}}, 1839 }} 1840 state.RunTransaction(c, s.State, ops) 1841 }, 1842 After: func() { 1843 assertApplicationBaseUpdate(c, app, state.UbuntuBase("22.04")) 1844 }, 1845 }, 1846 ).Check() 1847 1848 err := app.UpdateApplicationBase(state.UbuntuBase("22.04"), false) 1849 c.Assert(err, jc.ErrorIsNil) 1850 assertApplicationBaseUpdate(c, app, state.UbuntuBase("22.04")) 1851 } 1852 1853 func (s *ApplicationSuite) TestUpdateApplicationSeriesCharmURLChangedSeriesFail(c *gc.C) { 1854 app := s.setupCharmForTestUpdateApplicationBase(c, "multi-series") 1855 1856 defer state.SetTestHooks(c, s.State, 1857 jujutxn.TestHook{ 1858 Before: func() { 1859 v2 := state.AddTestingCharmMultiSeries(c, s.State, "multi-seriesv2") 1860 cfg := state.SetCharmConfig{ 1861 Charm: v2, 1862 CharmOrigin: defaultCharmOrigin(v2.URL()), 1863 } 1864 err := app.SetCharm(cfg) 1865 c.Assert(err, jc.ErrorIsNil) 1866 }, 1867 }, 1868 ).Check() 1869 1870 // Trusty is listed in only version 1 of the charm. 1871 err := app.UpdateApplicationBase(state.UbuntuBase("22.04"), false) 1872 c.Assert(err, gc.ErrorMatches, 1873 "updating application base: base \"ubuntu@22.04\" not supported by charm, "+ 1874 "the charm supported bases are: ubuntu@20.04, ubuntu@18.04") 1875 } 1876 1877 func (s *ApplicationSuite) TestUpdateApplicationSeriesCharmURLChangedSeriesPass(c *gc.C) { 1878 app := s.setupCharmForTestUpdateApplicationBase(c, "multi-series") 1879 1880 defer state.SetTestHooks(c, s.State, 1881 jujutxn.TestHook{ 1882 Before: func() { 1883 v2 := state.AddTestingCharmMultiSeries(c, s.State, "multi-seriesv2") 1884 origin := defaultCharmOrigin(v2.URL()) 1885 origin.Platform.OS = "ubuntu" 1886 origin.Platform.Channel = "18.04/stable" 1887 cfg := state.SetCharmConfig{ 1888 Charm: v2, 1889 CharmOrigin: origin, 1890 } 1891 err := app.SetCharm(cfg) 1892 c.Assert(err, jc.ErrorIsNil) 1893 }, 1894 }, 1895 ).Check() 1896 1897 // bionic is listed in both revisions of the charm. 1898 err := app.UpdateApplicationBase(state.UbuntuBase("18.04"), false) 1899 c.Assert(err, jc.ErrorIsNil) 1900 assertApplicationBaseUpdate(c, app, state.UbuntuBase("18.04")) 1901 } 1902 1903 func (s *ApplicationSuite) setupMultiSeriesUnitSubordinate(c *gc.C, app *state.Application, name string) *state.Application { 1904 unit, err := app.AddUnit(state.AddUnitParams{}) 1905 c.Assert(err, jc.ErrorIsNil) 1906 err = unit.AssignToNewMachine() 1907 c.Assert(err, jc.ErrorIsNil) 1908 return s.setupMultiSeriesUnitSubordinateGivenUnit(c, app, unit, name) 1909 } 1910 1911 func (s *ApplicationSuite) setupMultiSeriesUnitSubordinateGivenUnit(c *gc.C, app *state.Application, unit *state.Unit, name string) *state.Application { 1912 subApp := s.setupCharmForTestUpdateApplicationBase(c, name) 1913 1914 eps, err := s.State.InferEndpoints(app.Name(), name) 1915 c.Assert(err, jc.ErrorIsNil) 1916 rel, err := s.State.AddRelation(eps...) 1917 c.Assert(err, jc.ErrorIsNil) 1918 1919 ru, err := rel.Unit(unit) 1920 c.Assert(err, jc.ErrorIsNil) 1921 err = ru.EnterScope(nil) 1922 c.Assert(err, jc.ErrorIsNil) 1923 1924 err = app.Refresh() 1925 c.Assert(err, jc.ErrorIsNil) 1926 err = subApp.Refresh() 1927 c.Assert(err, jc.ErrorIsNil) 1928 err = unit.Refresh() 1929 c.Assert(err, jc.ErrorIsNil) 1930 1931 return subApp 1932 } 1933 1934 func assertApplicationBaseUpdate(c *gc.C, a *state.Application, base state.Base) { 1935 err := a.Refresh() 1936 c.Assert(err, jc.ErrorIsNil) 1937 stBase, err := corebase.ParseBase(base.OS, base.Channel) 1938 c.Assert(err, jc.ErrorIsNil) 1939 c.Assert(a.Base().String(), gc.Equals, stBase.String()) 1940 } 1941 1942 func (s *ApplicationSuite) TestUpdateApplicationSeriesWithSubordinate(c *gc.C) { 1943 app := s.setupCharmForTestUpdateApplicationBase(c, "multi-series") 1944 subApp := s.setupMultiSeriesUnitSubordinate(c, app, "multi-series-subordinate") 1945 err := app.UpdateApplicationBase(state.UbuntuBase("22.04"), false) 1946 c.Assert(err, jc.ErrorIsNil) 1947 assertApplicationBaseUpdate(c, app, state.UbuntuBase("22.04")) 1948 assertApplicationBaseUpdate(c, subApp, state.UbuntuBase("22.04")) 1949 } 1950 1951 func (s *ApplicationSuite) TestUpdateApplicationSeriesWithSubordinateFail(c *gc.C) { 1952 app := s.setupCharmForTestUpdateApplicationBase(c, "multi-series") 1953 subApp := s.setupMultiSeriesUnitSubordinate(c, app, "multi-series-subordinate") 1954 err := app.UpdateApplicationBase(state.UbuntuBase("16.04"), false) 1955 c.Assert(err, gc.ErrorMatches, `updating application base: base "ubuntu@16.04" not supported by charm.*`) 1956 assertApplicationBaseUpdate(c, app, state.UbuntuBase("20.04")) 1957 assertApplicationBaseUpdate(c, subApp, state.UbuntuBase("20.04")) 1958 } 1959 1960 func (s *ApplicationSuite) TestUpdateApplicationSeriesWithSubordinateForce(c *gc.C) { 1961 app := s.setupCharmForTestUpdateApplicationBase(c, "multi-series") 1962 subApp := s.setupMultiSeriesUnitSubordinate(c, app, "multi-series-subordinate") 1963 err := app.UpdateApplicationBase(state.UbuntuBase("16.04"), true) 1964 c.Assert(err, jc.ErrorIsNil) 1965 assertApplicationBaseUpdate(c, app, state.UbuntuBase("16.04")) 1966 assertApplicationBaseUpdate(c, subApp, state.UbuntuBase("16.04")) 1967 } 1968 1969 func (s *ApplicationSuite) TestUpdateApplicationSeriesUnitCountChange(c *gc.C) { 1970 app := s.setupCharmForTestUpdateApplicationBase(c, "multi-series") 1971 units, err := app.AllUnits() 1972 c.Assert(err, jc.ErrorIsNil) 1973 c.Assert(len(units), gc.Equals, 0) 1974 1975 defer state.SetTestHooks(c, s.State, 1976 jujutxn.TestHook{ 1977 Before: func() { 1978 // Add a subordinate and unit 1979 _ = s.setupMultiSeriesUnitSubordinate(c, app, "multi-series-subordinate") 1980 }, 1981 }, 1982 ).Check() 1983 1984 err = app.UpdateApplicationBase(state.UbuntuBase("22.04"), false) 1985 c.Assert(err, jc.ErrorIsNil) 1986 assertApplicationBaseUpdate(c, app, state.UbuntuBase("22.04")) 1987 1988 units, err = app.AllUnits() 1989 c.Assert(err, jc.ErrorIsNil) 1990 c.Assert(len(units), gc.Equals, 1) 1991 subApp, err := s.State.Application("multi-series-subordinate") 1992 c.Assert(err, jc.ErrorIsNil) 1993 assertApplicationBaseUpdate(c, subApp, state.UbuntuBase("22.04")) 1994 } 1995 1996 func (s *ApplicationSuite) TestUpdateApplicationSeriesSecondSubordinate(c *gc.C) { 1997 app := s.setupCharmForTestUpdateApplicationBase(c, "multi-series") 1998 subApp := s.setupMultiSeriesUnitSubordinate(c, app, "multi-series-subordinate") 1999 unit, err := s.State.Unit("multi-series/0") 2000 c.Assert(err, jc.ErrorIsNil) 2001 c.Assert(unit.SubordinateNames(), gc.DeepEquals, []string{"multi-series-subordinate/0"}) 2002 2003 defer state.SetTestHooks(c, s.State, 2004 jujutxn.TestHook{ 2005 Before: func() { 2006 // Add 2nd subordinate 2007 _ = s.setupMultiSeriesUnitSubordinateGivenUnit(c, app, unit, "multi-series-subordinate2") 2008 }, 2009 }, 2010 ).Check() 2011 2012 err = app.UpdateApplicationBase(state.UbuntuBase("22.04"), false) 2013 c.Assert(err, jc.ErrorIsNil) 2014 assertApplicationBaseUpdate(c, app, state.UbuntuBase("22.04")) 2015 assertApplicationBaseUpdate(c, subApp, state.UbuntuBase("22.04")) 2016 2017 subApp2, err := s.State.Application("multi-series-subordinate2") 2018 c.Assert(err, jc.ErrorIsNil) 2019 assertApplicationBaseUpdate(c, subApp2, state.UbuntuBase("22.04")) 2020 } 2021 2022 func (s *ApplicationSuite) TestUpdateApplicationSeriesSecondSubordinateIncompatible(c *gc.C) { 2023 app := s.setupCharmForTestUpdateApplicationBase(c, "multi-series") 2024 subApp := s.setupMultiSeriesUnitSubordinate(c, app, "multi-series-subordinate") 2025 unit, err := s.State.Unit("multi-series/0") 2026 c.Assert(err, jc.ErrorIsNil) 2027 c.Assert(unit.SubordinateNames(), gc.DeepEquals, []string{"multi-series-subordinate/0"}) 2028 2029 defer state.SetTestHooks(c, s.State, 2030 jujutxn.TestHook{ 2031 Before: func() { 2032 // Add 2nd subordinate 2033 _ = s.setupMultiSeriesUnitSubordinateGivenUnit(c, app, unit, "multi-series-subordinate2") 2034 }, 2035 }, 2036 ).Check() 2037 2038 err = app.UpdateApplicationBase(state.UbuntuBase("18.04"), false) 2039 c.Assert(err, gc.ErrorMatches, `updating application base: base "ubuntu@18.04" not supported by charm.*`) 2040 assertApplicationBaseUpdate(c, app, state.UbuntuBase("20.04")) 2041 assertApplicationBaseUpdate(c, subApp, state.UbuntuBase("20.04")) 2042 2043 subApp2, err := s.State.Application("multi-series-subordinate2") 2044 c.Assert(err, jc.ErrorIsNil) 2045 assertApplicationBaseUpdate(c, subApp2, state.UbuntuBase("20.04")) 2046 } 2047 2048 func assertNoSettingsRef(c *gc.C, st *state.State, appName string, sch *state.Charm) { 2049 cURL := sch.URL() 2050 _, err := state.ApplicationSettingsRefCount(st, appName, &cURL) 2051 c.Assert(errors.Cause(err), jc.Satisfies, errors.IsNotFound) 2052 } 2053 2054 func assertSettingsRef(c *gc.C, st *state.State, appName string, sch *state.Charm, refcount int) { 2055 cURL := sch.URL() 2056 rc, err := state.ApplicationSettingsRefCount(st, appName, &cURL) 2057 c.Assert(err, jc.ErrorIsNil) 2058 c.Assert(rc, gc.Equals, refcount) 2059 } 2060 2061 func (s *ApplicationSuite) TestSettingsRefCountWorks(c *gc.C) { 2062 // This test ensures the application settings per charm URL are 2063 // properly reference counted. 2064 oldCh := s.AddConfigCharm(c, "wordpress", emptyConfig, 1) 2065 newCh := s.AddConfigCharm(c, "wordpress", emptyConfig, 2) 2066 appName := "mywp" 2067 2068 // Both refcounts are zero initially. 2069 assertNoSettingsRef(c, s.State, appName, oldCh) 2070 assertNoSettingsRef(c, s.State, appName, newCh) 2071 2072 // app is using oldCh, so its settings refcount is incremented. 2073 app := s.AddTestingApplication(c, appName, oldCh) 2074 assertSettingsRef(c, s.State, appName, oldCh, 1) 2075 assertNoSettingsRef(c, s.State, appName, newCh) 2076 2077 // Changing to the same charm does not change the refcount. 2078 cfg := state.SetCharmConfig{ 2079 Charm: oldCh, 2080 CharmOrigin: defaultCharmOrigin(oldCh.URL()), 2081 } 2082 err := app.SetCharm(cfg) 2083 c.Assert(err, jc.ErrorIsNil) 2084 assertSettingsRef(c, s.State, appName, oldCh, 1) 2085 assertNoSettingsRef(c, s.State, appName, newCh) 2086 2087 // Changing from oldCh to newCh causes the refcount of oldCh's 2088 // settings to be decremented, while newCh's settings is 2089 // incremented. Consequently, because oldCh's refcount is 0, the 2090 // settings doc will be removed. 2091 cfg = state.SetCharmConfig{ 2092 Charm: newCh, 2093 CharmOrigin: defaultCharmOrigin(newCh.URL()), 2094 } 2095 err = app.SetCharm(cfg) 2096 c.Assert(err, jc.ErrorIsNil) 2097 assertNoSettingsRef(c, s.State, appName, oldCh) 2098 assertSettingsRef(c, s.State, appName, newCh, 1) 2099 2100 // The same but newCh swapped with oldCh. 2101 cfg = state.SetCharmConfig{ 2102 Charm: oldCh, 2103 CharmOrigin: defaultCharmOrigin(oldCh.URL()), 2104 } 2105 err = app.SetCharm(cfg) 2106 c.Assert(err, jc.ErrorIsNil) 2107 assertSettingsRef(c, s.State, appName, oldCh, 1) 2108 assertNoSettingsRef(c, s.State, appName, newCh) 2109 2110 // Adding a unit without a charm URL set does not affect the 2111 // refcount. 2112 u, err := app.AddUnit(state.AddUnitParams{}) 2113 c.Assert(err, jc.ErrorIsNil) 2114 charmURL := u.CharmURL() 2115 c.Assert(charmURL, gc.IsNil) 2116 assertSettingsRef(c, s.State, appName, oldCh, 1) 2117 assertNoSettingsRef(c, s.State, appName, newCh) 2118 2119 // Setting oldCh as the units charm URL increments oldCh, which is 2120 // used by app as well, hence 2. 2121 err = u.SetCharmURL(oldCh.URL()) 2122 c.Assert(err, jc.ErrorIsNil) 2123 charmURL = u.CharmURL() 2124 c.Assert(charmURL, gc.NotNil) 2125 c.Assert(*charmURL, gc.Equals, oldCh.URL()) 2126 assertSettingsRef(c, s.State, appName, oldCh, 2) 2127 assertNoSettingsRef(c, s.State, appName, newCh) 2128 2129 // A dead unit does not decrement the refcount. 2130 err = u.EnsureDead() 2131 c.Assert(err, jc.ErrorIsNil) 2132 assertSettingsRef(c, s.State, appName, oldCh, 2) 2133 assertNoSettingsRef(c, s.State, appName, newCh) 2134 2135 // Once the unit is removed, refcount is decremented. 2136 err = u.Remove() 2137 c.Assert(err, jc.ErrorIsNil) 2138 assertSettingsRef(c, s.State, appName, oldCh, 1) 2139 assertNoSettingsRef(c, s.State, appName, newCh) 2140 2141 // Finally, after the application is destroyed and removed (since the 2142 // last unit's gone), the refcount is again decremented. 2143 err = app.Destroy() 2144 c.Assert(err, jc.ErrorIsNil) 2145 assertNoSettingsRef(c, s.State, appName, oldCh) 2146 assertNoSettingsRef(c, s.State, appName, newCh) 2147 2148 // Having studiously avoided triggering cleanups throughout, 2149 // invoke them now and check that the charms are cleaned up 2150 // correctly -- and that a storm of cleanups for the same 2151 // charm are not a problem. 2152 err = s.State.Cleanup() 2153 c.Assert(err, jc.ErrorIsNil) 2154 err = oldCh.Refresh() 2155 c.Assert(err, jc.Satisfies, errors.IsNotFound) 2156 err = newCh.Refresh() 2157 c.Assert(err, jc.Satisfies, errors.IsNotFound) 2158 } 2159 2160 func (s *ApplicationSuite) TestSettingsRefCreateRace(c *gc.C) { 2161 oldCh := s.AddConfigCharm(c, "wordpress", emptyConfig, 1) 2162 newCh := s.AddConfigCharm(c, "wordpress", emptyConfig, 2) 2163 appName := "mywp" 2164 2165 app := s.AddTestingApplication(c, appName, oldCh) 2166 unit, err := app.AddUnit(state.AddUnitParams{}) 2167 c.Assert(err, jc.ErrorIsNil) 2168 2169 // just before setting the unit charm url, switch the application 2170 // away from the original charm, causing the attempt to fail 2171 // (because the settings have gone away; it's the unit's job to 2172 // fail out and handle the new charm when it comes back up 2173 // again). 2174 dropSettings := func() { 2175 cfg := state.SetCharmConfig{ 2176 Charm: newCh, 2177 CharmOrigin: defaultCharmOrigin(newCh.URL()), 2178 } 2179 err = app.SetCharm(cfg) 2180 c.Assert(err, jc.ErrorIsNil) 2181 } 2182 defer state.SetBeforeHooks(c, s.State, dropSettings).Check() 2183 2184 err = unit.SetCharmURL(oldCh.URL()) 2185 c.Check(err, gc.ErrorMatches, "settings reference: does not exist") 2186 } 2187 2188 func (s *ApplicationSuite) TestSettingsRefRemoveRace(c *gc.C) { 2189 oldCh := s.AddConfigCharm(c, "wordpress", emptyConfig, 1) 2190 newCh := s.AddConfigCharm(c, "wordpress", emptyConfig, 2) 2191 appName := "mywp" 2192 2193 app := s.AddTestingApplication(c, appName, oldCh) 2194 unit, err := app.AddUnit(state.AddUnitParams{}) 2195 c.Assert(err, jc.ErrorIsNil) 2196 2197 // just before updating the app charm url, set that charm url on 2198 // a unit to block the removal. 2199 grabReference := func() { 2200 err := unit.SetCharmURL(oldCh.URL()) 2201 c.Assert(err, jc.ErrorIsNil) 2202 } 2203 defer state.SetBeforeHooks(c, s.State, grabReference).Check() 2204 2205 cfg := state.SetCharmConfig{ 2206 Charm: newCh, 2207 CharmOrigin: defaultCharmOrigin(newCh.URL()), 2208 } 2209 err = app.SetCharm(cfg) 2210 c.Assert(err, jc.ErrorIsNil) 2211 2212 // check refs to both settings exist 2213 assertSettingsRef(c, s.State, appName, oldCh, 1) 2214 assertSettingsRef(c, s.State, appName, newCh, 1) 2215 } 2216 2217 func assertNoOffersRef(c *gc.C, st *state.State, appName string) { 2218 _, err := state.ApplicationOffersRefCount(st, appName) 2219 c.Assert(errors.Cause(err), jc.Satisfies, errors.IsNotFound) 2220 } 2221 2222 func assertOffersRef(c *gc.C, st *state.State, appName string, refcount int) { 2223 rc, err := state.ApplicationOffersRefCount(st, appName) 2224 c.Assert(err, jc.ErrorIsNil) 2225 c.Assert(rc, gc.Equals, refcount) 2226 } 2227 2228 func (s *ApplicationSuite) TestOffersRefCountWorks(c *gc.C) { 2229 // Refcounts are zero initially. 2230 assertNoOffersRef(c, s.State, "mysql") 2231 2232 ao := state.NewApplicationOffers(s.State) 2233 _, err := ao.AddOffer(crossmodel.AddApplicationOfferArgs{ 2234 OfferName: "hosted-mysql", 2235 ApplicationName: "mysql", 2236 Endpoints: map[string]string{"server": "server"}, 2237 Owner: s.Owner.Id(), 2238 }) 2239 c.Assert(err, jc.ErrorIsNil) 2240 assertOffersRef(c, s.State, "mysql", 1) 2241 2242 _, err = ao.AddOffer(crossmodel.AddApplicationOfferArgs{ 2243 OfferName: "mysql-offer", 2244 ApplicationName: "mysql", 2245 Endpoints: map[string]string{"server": "server"}, 2246 Owner: s.Owner.Id(), 2247 }) 2248 c.Assert(err, jc.ErrorIsNil) 2249 assertOffersRef(c, s.State, "mysql", 2) 2250 2251 // Once the offer is removed, refcount is decremented. 2252 err = ao.Remove("hosted-mysql", false) 2253 c.Assert(err, jc.ErrorIsNil) 2254 assertOffersRef(c, s.State, "mysql", 1) 2255 2256 // Trying to destroy the app while there is an offer 2257 // succeeds when that offer has no connections 2258 err = s.mysql.Destroy() 2259 c.Assert(err, jc.ErrorIsNil) 2260 assertRemoved(c, s.mysql) 2261 assertNoOffersRef(c, s.State, "mysql") 2262 } 2263 2264 func (s *ApplicationSuite) TestDestroyApplicationRemoveOffers(c *gc.C) { 2265 // Refcounts are zero initially. 2266 assertNoOffersRef(c, s.State, "mysql") 2267 2268 ao := state.NewApplicationOffers(s.State) 2269 _, err := ao.AddOffer(crossmodel.AddApplicationOfferArgs{ 2270 OfferName: "hosted-mysql", 2271 ApplicationName: "mysql", 2272 Endpoints: map[string]string{"server": "server"}, 2273 Owner: s.Owner.Id(), 2274 }) 2275 c.Assert(err, jc.ErrorIsNil) 2276 assertOffersRef(c, s.State, "mysql", 1) 2277 2278 _, err = ao.AddOffer(crossmodel.AddApplicationOfferArgs{ 2279 OfferName: "mysql-offer", 2280 ApplicationName: "mysql", 2281 Endpoints: map[string]string{"server": "server"}, 2282 Owner: s.Owner.Id(), 2283 }) 2284 c.Assert(err, jc.ErrorIsNil) 2285 assertOffersRef(c, s.State, "mysql", 2) 2286 2287 op := s.mysql.DestroyOperation() 2288 op.RemoveOffers = true 2289 err = s.State.ApplyOperation(op) 2290 c.Assert(err, jc.ErrorIsNil) 2291 assertRemoved(c, s.mysql) 2292 assertNoOffersRef(c, s.State, "mysql") 2293 2294 offers, err := ao.AllApplicationOffers() 2295 c.Assert(err, jc.ErrorIsNil) 2296 c.Assert(offers, gc.HasLen, 0) 2297 } 2298 2299 func (s *ApplicationSuite) TestOffersRefRace(c *gc.C) { 2300 addOffer := func() { 2301 ao := state.NewApplicationOffers(s.State) 2302 _, err := ao.AddOffer(crossmodel.AddApplicationOfferArgs{ 2303 OfferName: "hosted-mysql", 2304 ApplicationName: "mysql", 2305 Endpoints: map[string]string{"server": "server"}, 2306 Owner: s.Owner.Id(), 2307 }) 2308 c.Assert(err, jc.ErrorIsNil) 2309 } 2310 defer state.SetBeforeHooks(c, s.State, addOffer).Check() 2311 2312 err := s.mysql.Destroy() 2313 c.Assert(err, jc.ErrorIsNil) 2314 assertRemoved(c, s.mysql) 2315 assertNoOffersRef(c, s.State, "mysql") 2316 } 2317 2318 func (s *ApplicationSuite) TestOffersRefRaceWithForce(c *gc.C) { 2319 addOffer := func() { 2320 ao := state.NewApplicationOffers(s.State) 2321 _, err := ao.AddOffer(crossmodel.AddApplicationOfferArgs{ 2322 OfferName: "hosted-mysql", 2323 ApplicationName: "mysql", 2324 Endpoints: map[string]string{"server": "server"}, 2325 Owner: s.Owner.Id(), 2326 }) 2327 c.Assert(err, jc.ErrorIsNil) 2328 } 2329 defer state.SetBeforeHooks(c, s.State, addOffer).Check() 2330 2331 op := s.mysql.DestroyOperation() 2332 op.Force = true 2333 err := s.State.ApplyOperation(op) 2334 c.Assert(err, jc.ErrorIsNil) 2335 assertRemoved(c, s.mysql) 2336 assertNoOffersRef(c, s.State, "mysql") 2337 } 2338 2339 const mysqlBaseMeta = ` 2340 name: mysql 2341 summary: "Database engine" 2342 description: "A pretty popular database" 2343 provides: 2344 server: mysql 2345 ` 2346 const onePeerMeta = ` 2347 peers: 2348 cluster: mysql 2349 ` 2350 const onePeerAltMeta = ` 2351 peers: 2352 minion: helper 2353 ` 2354 const twoPeersMeta = ` 2355 peers: 2356 cluster: mysql 2357 loadbalancer: phony 2358 ` 2359 2360 func (s *ApplicationSuite) assertApplicationRelations(c *gc.C, app *state.Application, expectedKeys ...string) []*state.Relation { 2361 rels, err := app.Relations() 2362 c.Assert(err, jc.ErrorIsNil) 2363 if len(rels) == 0 { 2364 return nil 2365 } 2366 relKeys := make([]string, len(expectedKeys)) 2367 for i, rel := range rels { 2368 relKeys[i] = rel.String() 2369 } 2370 sort.Strings(relKeys) 2371 c.Assert(relKeys, gc.DeepEquals, expectedKeys) 2372 return rels 2373 } 2374 2375 func (s *ApplicationSuite) TestNewPeerRelationsAddedOnUpgrade(c *gc.C) { 2376 // Original mysql charm has no peer relations. 2377 oldCh := s.AddMetaCharm(c, "mysql", mysqlBaseMeta+onePeerMeta, 2) 2378 newCh := s.AddMetaCharm(c, "mysql", mysqlBaseMeta+twoPeersMeta, 3) 2379 2380 // No relations joined yet. 2381 s.assertApplicationRelations(c, s.mysql) 2382 2383 cfg := state.SetCharmConfig{Charm: oldCh, CharmOrigin: defaultCharmOrigin(oldCh.URL())} 2384 err := s.mysql.SetCharm(cfg) 2385 c.Assert(err, jc.ErrorIsNil) 2386 s.assertApplicationRelations(c, s.mysql, "mysql:cluster") 2387 2388 cfg = state.SetCharmConfig{Charm: newCh, CharmOrigin: defaultCharmOrigin(newCh.URL())} 2389 err = s.mysql.SetCharm(cfg) 2390 c.Assert(err, jc.ErrorIsNil) 2391 rels := s.assertApplicationRelations(c, s.mysql, "mysql:cluster", "mysql:loadbalancer") 2392 2393 // Check state consistency by attempting to destroy the application. 2394 err = s.mysql.Destroy() 2395 c.Assert(err, jc.ErrorIsNil) 2396 assertRemoved(c, s.mysql) 2397 2398 // Check the peer relations got destroyed as well. 2399 for _, rel := range rels { 2400 err = rel.Refresh() 2401 c.Assert(err, jc.Satisfies, errors.IsNotFound) 2402 } 2403 } 2404 2405 func (s *ApplicationSuite) TestStalePeerRelationsRemovedOnUpgrade(c *gc.C) { 2406 // Original mysql charm has no peer relations. 2407 // oldCh is mysql + the peer relation "mysql:cluster" 2408 // newCh is mysql + the peer relation "mysql:minion" 2409 oldCh := s.AddMetaCharm(c, "mysql", mysqlBaseMeta+onePeerMeta, 2) 2410 newCh := s.AddMetaCharm(c, "mysql", mysqlBaseMeta+onePeerAltMeta, 42) 2411 2412 // No relations joined yet. 2413 s.assertApplicationRelations(c, s.mysql) 2414 2415 cfg := state.SetCharmConfig{Charm: oldCh, CharmOrigin: defaultCharmOrigin(oldCh.URL())} 2416 err := s.mysql.SetCharm(cfg) 2417 c.Assert(err, jc.ErrorIsNil) 2418 s.assertApplicationRelations(c, s.mysql, "mysql:cluster") 2419 2420 // Since the two charms have different URLs, the following SetCharm call 2421 // emulates a "juju refresh --switch" request. We expect that any prior 2422 // peer relations that are not referenced by the new charm metadata 2423 // to be dropped and any new peer relations to be created. 2424 cfg = state.SetCharmConfig{ 2425 Charm: newCh, 2426 CharmOrigin: defaultCharmOrigin(newCh.URL()), 2427 ForceUnits: true, 2428 } 2429 err = s.mysql.SetCharm(cfg) 2430 c.Assert(err, jc.ErrorIsNil) 2431 2432 rels := s.assertApplicationRelations(c, s.mysql, "mysql:minion") 2433 2434 // Check state consistency by attempting to destroy the application. 2435 err = s.mysql.Destroy() 2436 c.Assert(err, jc.ErrorIsNil) 2437 assertRemoved(c, s.mysql) 2438 2439 // Check the peer relations got destroyed as well. 2440 for _, rel := range rels { 2441 err = rel.Refresh() 2442 c.Assert(err, jc.Satisfies, errors.IsNotFound) 2443 } 2444 } 2445 2446 func jujuInfoEp(applicationname string) state.Endpoint { 2447 return state.Endpoint{ 2448 ApplicationName: applicationname, 2449 Relation: charm.Relation{ 2450 Interface: "juju-info", 2451 Name: "juju-info", 2452 Role: charm.RoleProvider, 2453 Scope: charm.ScopeGlobal, 2454 }, 2455 } 2456 } 2457 2458 func (s *ApplicationSuite) TestTag(c *gc.C) { 2459 c.Assert(s.mysql.Tag().String(), gc.Equals, "application-mysql") 2460 } 2461 2462 func (s *ApplicationSuite) TestMysqlEndpoints(c *gc.C) { 2463 _, err := s.mysql.Endpoint("mysql") 2464 c.Assert(err, gc.ErrorMatches, `application "mysql" has no "mysql" relation`) 2465 2466 jiEP, err := s.mysql.Endpoint("juju-info") 2467 c.Assert(err, jc.ErrorIsNil) 2468 c.Assert(jiEP, gc.DeepEquals, jujuInfoEp("mysql")) 2469 2470 serverEP, err := s.mysql.Endpoint("server") 2471 c.Assert(err, jc.ErrorIsNil) 2472 c.Assert(serverEP, gc.DeepEquals, state.Endpoint{ 2473 ApplicationName: "mysql", 2474 Relation: charm.Relation{ 2475 Interface: "mysql", 2476 Name: "server", 2477 Role: charm.RoleProvider, 2478 Scope: charm.ScopeGlobal, 2479 }, 2480 }) 2481 serverAdminEP, err := s.mysql.Endpoint("server-admin") 2482 c.Assert(err, jc.ErrorIsNil) 2483 c.Assert(serverAdminEP, gc.DeepEquals, state.Endpoint{ 2484 ApplicationName: "mysql", 2485 Relation: charm.Relation{ 2486 Interface: "mysql-root", 2487 Name: "server-admin", 2488 Role: charm.RoleProvider, 2489 Scope: charm.ScopeGlobal, 2490 }, 2491 }) 2492 monitoringEP, err := s.mysql.Endpoint("metrics-client") 2493 c.Assert(err, jc.ErrorIsNil) 2494 c.Assert(monitoringEP, gc.DeepEquals, state.Endpoint{ 2495 ApplicationName: "mysql", 2496 Relation: charm.Relation{ 2497 Interface: "metrics", 2498 Name: "metrics-client", 2499 Role: charm.RoleRequirer, 2500 Scope: charm.ScopeGlobal, 2501 }, 2502 }) 2503 2504 eps, err := s.mysql.Endpoints() 2505 c.Assert(err, jc.ErrorIsNil) 2506 c.Assert(eps, jc.SameContents, []state.Endpoint{jiEP, serverEP, serverAdminEP, monitoringEP}) 2507 } 2508 2509 func (s *ApplicationSuite) TestRiakEndpoints(c *gc.C) { 2510 riak := s.AddTestingApplication(c, "myriak", s.AddTestingCharm(c, "riak")) 2511 2512 _, err := riak.Endpoint("garble") 2513 c.Assert(err, gc.ErrorMatches, `application "myriak" has no "garble" relation`) 2514 2515 jiEP, err := riak.Endpoint("juju-info") 2516 c.Assert(err, jc.ErrorIsNil) 2517 c.Assert(jiEP, gc.DeepEquals, jujuInfoEp("myriak")) 2518 2519 ringEP, err := riak.Endpoint("ring") 2520 c.Assert(err, jc.ErrorIsNil) 2521 c.Assert(ringEP, gc.DeepEquals, state.Endpoint{ 2522 ApplicationName: "myriak", 2523 Relation: charm.Relation{ 2524 Interface: "riak", 2525 Name: "ring", 2526 Role: charm.RolePeer, 2527 Scope: charm.ScopeGlobal, 2528 }, 2529 }) 2530 2531 adminEP, err := riak.Endpoint("admin") 2532 c.Assert(err, jc.ErrorIsNil) 2533 c.Assert(adminEP, gc.DeepEquals, state.Endpoint{ 2534 ApplicationName: "myriak", 2535 Relation: charm.Relation{ 2536 Interface: "http", 2537 Name: "admin", 2538 Role: charm.RoleProvider, 2539 Scope: charm.ScopeGlobal, 2540 }, 2541 }) 2542 2543 endpointEP, err := riak.Endpoint("endpoint") 2544 c.Assert(err, jc.ErrorIsNil) 2545 c.Assert(endpointEP, gc.DeepEquals, state.Endpoint{ 2546 ApplicationName: "myriak", 2547 Relation: charm.Relation{ 2548 Interface: "http", 2549 Name: "endpoint", 2550 Role: charm.RoleProvider, 2551 Scope: charm.ScopeGlobal, 2552 }, 2553 }) 2554 2555 eps, err := riak.Endpoints() 2556 c.Assert(err, jc.ErrorIsNil) 2557 c.Assert(eps, gc.DeepEquals, []state.Endpoint{adminEP, endpointEP, jiEP, ringEP}) 2558 } 2559 2560 func (s *ApplicationSuite) TestWordpressEndpoints(c *gc.C) { 2561 wordpress := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 2562 2563 _, err := wordpress.Endpoint("nonsense") 2564 c.Assert(err, gc.ErrorMatches, `application "wordpress" has no "nonsense" relation`) 2565 2566 jiEP, err := wordpress.Endpoint("juju-info") 2567 c.Assert(err, jc.ErrorIsNil) 2568 c.Assert(jiEP, gc.DeepEquals, jujuInfoEp("wordpress")) 2569 2570 urlEP, err := wordpress.Endpoint("url") 2571 c.Assert(err, jc.ErrorIsNil) 2572 c.Assert(urlEP, gc.DeepEquals, state.Endpoint{ 2573 ApplicationName: "wordpress", 2574 Relation: charm.Relation{ 2575 Interface: "http", 2576 Name: "url", 2577 Role: charm.RoleProvider, 2578 Scope: charm.ScopeGlobal, 2579 }, 2580 }) 2581 2582 ldEP, err := wordpress.Endpoint("logging-dir") 2583 c.Assert(err, jc.ErrorIsNil) 2584 c.Assert(ldEP, gc.DeepEquals, state.Endpoint{ 2585 ApplicationName: "wordpress", 2586 Relation: charm.Relation{ 2587 Interface: "logging", 2588 Name: "logging-dir", 2589 Role: charm.RoleProvider, 2590 Scope: charm.ScopeContainer, 2591 }, 2592 }) 2593 2594 mpEP, err := wordpress.Endpoint("monitoring-port") 2595 c.Assert(err, jc.ErrorIsNil) 2596 c.Assert(mpEP, gc.DeepEquals, state.Endpoint{ 2597 ApplicationName: "wordpress", 2598 Relation: charm.Relation{ 2599 Interface: "monitoring", 2600 Name: "monitoring-port", 2601 Role: charm.RoleProvider, 2602 Scope: charm.ScopeContainer, 2603 }, 2604 }) 2605 2606 dbEP, err := wordpress.Endpoint("db") 2607 c.Assert(err, jc.ErrorIsNil) 2608 c.Assert(dbEP, gc.DeepEquals, state.Endpoint{ 2609 ApplicationName: "wordpress", 2610 Relation: charm.Relation{ 2611 Interface: "mysql", 2612 Name: "db", 2613 Role: charm.RoleRequirer, 2614 Scope: charm.ScopeGlobal, 2615 Limit: 1, 2616 }, 2617 }) 2618 2619 cacheEP, err := wordpress.Endpoint("cache") 2620 c.Assert(err, jc.ErrorIsNil) 2621 c.Assert(cacheEP, gc.DeepEquals, state.Endpoint{ 2622 ApplicationName: "wordpress", 2623 Relation: charm.Relation{ 2624 Interface: "varnish", 2625 Name: "cache", 2626 Role: charm.RoleRequirer, 2627 Scope: charm.ScopeGlobal, 2628 Limit: 2, 2629 Optional: true, 2630 }, 2631 }) 2632 2633 eps, err := wordpress.Endpoints() 2634 c.Assert(err, jc.ErrorIsNil) 2635 c.Assert(eps, gc.DeepEquals, []state.Endpoint{cacheEP, dbEP, jiEP, ldEP, mpEP, urlEP}) 2636 } 2637 2638 func (s *ApplicationSuite) TestApplicationRefresh(c *gc.C) { 2639 s1, err := s.State.Application(s.mysql.Name()) 2640 c.Assert(err, jc.ErrorIsNil) 2641 2642 cfg := state.SetCharmConfig{ 2643 Charm: s.charm, 2644 CharmOrigin: defaultCharmOrigin(s.charm.URL()), 2645 ForceUnits: true, 2646 } 2647 2648 err = s.mysql.SetCharm(cfg) 2649 c.Assert(err, jc.ErrorIsNil) 2650 2651 testch, force, err := s1.Charm() 2652 c.Assert(err, jc.ErrorIsNil) 2653 c.Assert(force, jc.IsFalse) 2654 c.Assert(testch.URL(), gc.DeepEquals, s.charm.URL()) 2655 2656 err = s1.Refresh() 2657 c.Assert(err, jc.ErrorIsNil) 2658 testch, force, err = s1.Charm() 2659 c.Assert(err, jc.ErrorIsNil) 2660 c.Assert(force, jc.IsTrue) 2661 c.Assert(testch.URL(), gc.DeepEquals, s.charm.URL()) 2662 2663 err = s.mysql.Destroy() 2664 c.Assert(err, jc.ErrorIsNil) 2665 assertRemoved(c, s.mysql) 2666 } 2667 2668 func (s *ApplicationSuite) TestSetPassword(c *gc.C) { 2669 testSetPassword(c, func() (state.Authenticator, error) { 2670 return s.State.Application(s.mysql.Name()) 2671 }) 2672 } 2673 2674 func (s *ApplicationSuite) TestApplicationExposed(c *gc.C) { 2675 // Check that querying for the exposed flag works correctly. 2676 c.Assert(s.mysql.IsExposed(), jc.IsFalse) 2677 2678 // Check that setting and clearing the exposed flag works correctly. 2679 err := s.mysql.MergeExposeSettings(nil) 2680 c.Assert(err, jc.ErrorIsNil) 2681 c.Assert(s.mysql.IsExposed(), jc.IsTrue) 2682 err = s.mysql.ClearExposed() 2683 c.Assert(err, jc.ErrorIsNil) 2684 c.Assert(s.mysql.IsExposed(), jc.IsFalse) 2685 2686 // Check that setting and clearing the exposed flag repeatedly does not fail. 2687 err = s.mysql.MergeExposeSettings(nil) 2688 c.Assert(err, jc.ErrorIsNil) 2689 err = s.mysql.MergeExposeSettings(nil) 2690 c.Assert(err, jc.ErrorIsNil) 2691 err = s.mysql.ClearExposed() 2692 c.Assert(err, jc.ErrorIsNil) 2693 err = s.mysql.ClearExposed() 2694 c.Assert(err, jc.ErrorIsNil) 2695 err = s.mysql.MergeExposeSettings(nil) 2696 c.Assert(err, jc.ErrorIsNil) 2697 c.Assert(s.mysql.IsExposed(), jc.IsTrue) 2698 2699 // Make the application Dying and check that ClearExposed and MergeExposeSettings fail. 2700 // TODO(fwereade): maybe application destruction should always unexpose? 2701 u, err := s.mysql.AddUnit(state.AddUnitParams{}) 2702 c.Assert(err, jc.ErrorIsNil) 2703 err = s.mysql.Destroy() 2704 c.Assert(err, jc.ErrorIsNil) 2705 assertLife(c, s.mysql, state.Dying) 2706 err = s.mysql.ClearExposed() 2707 c.Assert(err, gc.ErrorMatches, notAliveErr) 2708 err = s.mysql.MergeExposeSettings(nil) 2709 c.Assert(err, gc.ErrorMatches, notAliveErr) 2710 2711 // Remove the application and check that both fail. 2712 err = u.EnsureDead() 2713 c.Assert(err, jc.ErrorIsNil) 2714 err = u.Remove() 2715 c.Assert(err, jc.ErrorIsNil) 2716 err = s.mysql.MergeExposeSettings(nil) 2717 c.Assert(err, gc.ErrorMatches, notAliveErr) 2718 err = s.mysql.ClearExposed() 2719 c.Assert(err, gc.ErrorMatches, notAliveErr) 2720 } 2721 2722 func (s *ApplicationSuite) TestApplicationExposeEndpoints(c *gc.C) { 2723 // Check that querying for the exposed flag works correctly. 2724 c.Assert(s.mysql.IsExposed(), jc.IsFalse) 2725 2726 // Check argument validation 2727 err := s.mysql.MergeExposeSettings(map[string]state.ExposedEndpoint{ 2728 "": {}, 2729 "bogus-endpoint": {}, 2730 }) 2731 c.Assert(err, gc.ErrorMatches, `.*endpoint "bogus-endpoint" not found`) 2732 err = s.mysql.MergeExposeSettings(map[string]state.ExposedEndpoint{ 2733 "server": {ExposeToSpaceIDs: []string{"bogus-space-id"}}, 2734 }) 2735 c.Assert(err, gc.ErrorMatches, `.*space with ID "bogus-space-id" not found`) 2736 err = s.mysql.MergeExposeSettings(map[string]state.ExposedEndpoint{ 2737 "server": {ExposeToCIDRs: []string{"not-a-cidr"}}, 2738 }) 2739 c.Assert(err, gc.ErrorMatches, `.*unable to parse "not-a-cidr" as a CIDR.*`) 2740 2741 // Check that the expose parameters are properly persisted 2742 exp := map[string]state.ExposedEndpoint{ 2743 "server": { 2744 ExposeToSpaceIDs: []string{network.AlphaSpaceId}, 2745 ExposeToCIDRs: []string{"13.37.0.0/16"}, 2746 }, 2747 } 2748 err = s.mysql.MergeExposeSettings(exp) 2749 c.Assert(err, jc.ErrorIsNil) 2750 2751 c.Assert(s.mysql.ExposedEndpoints(), gc.DeepEquals, exp) 2752 2753 // Refresh model and ensure that we get the same parameters 2754 err = s.mysql.Refresh() 2755 c.Assert(err, jc.ErrorIsNil) 2756 c.Assert(s.mysql.ExposedEndpoints(), gc.DeepEquals, exp) 2757 } 2758 2759 func (s *ApplicationSuite) TestApplicationExposeEndpointMergeLogic(c *gc.C) { 2760 // Check that querying for the exposed flag works correctly. 2761 c.Assert(s.mysql.IsExposed(), jc.IsFalse) 2762 2763 // Set initial value 2764 initial := map[string]state.ExposedEndpoint{ 2765 "server": { 2766 ExposeToSpaceIDs: []string{network.AlphaSpaceId}, 2767 ExposeToCIDRs: []string{"13.37.0.0/16"}, 2768 }, 2769 } 2770 err := s.mysql.MergeExposeSettings(initial) 2771 c.Assert(err, jc.ErrorIsNil) 2772 c.Assert(s.mysql.ExposedEndpoints(), gc.DeepEquals, initial) 2773 2774 // The merge call should overwrite the "server" value and append the 2775 // entry for "server-admin" 2776 updated := map[string]state.ExposedEndpoint{ 2777 "server": { 2778 ExposeToCIDRs: []string{"0.0.0.0/0"}, 2779 }, 2780 "server-admin": { 2781 ExposeToSpaceIDs: []string{network.AlphaSpaceId}, 2782 ExposeToCIDRs: []string{"13.37.0.0/16"}, 2783 }, 2784 } 2785 err = s.mysql.MergeExposeSettings(updated) 2786 c.Assert(err, jc.ErrorIsNil) 2787 c.Assert(s.mysql.ExposedEndpoints(), gc.DeepEquals, updated) 2788 } 2789 2790 func (s *ApplicationSuite) TestApplicationExposeWithoutSpaceAndCIDR(c *gc.C) { 2791 // Check that querying for the exposed flag works correctly. 2792 c.Assert(s.mysql.IsExposed(), jc.IsFalse) 2793 2794 err := s.mysql.MergeExposeSettings(map[string]state.ExposedEndpoint{ 2795 // If the expose params are empty, an implicit 0.0.0.0/0 will 2796 // be assumed (equivalent to: juju expose --endpoints server) 2797 "server": {}, 2798 }) 2799 c.Assert(err, jc.ErrorIsNil) 2800 2801 exp := map[string]state.ExposedEndpoint{ 2802 "server": { 2803 ExposeToCIDRs: []string{firewall.AllNetworksIPV4CIDR, firewall.AllNetworksIPV6CIDR}, 2804 }, 2805 } 2806 c.Assert(s.mysql.ExposedEndpoints(), gc.DeepEquals, exp, gc.Commentf("expected the implicit 0.0.0.0/0 and ::/0 CIDRs to be added when an empty ExposedEndpoint value is provided to MergeExposeSettings")) 2807 } 2808 2809 func (s *ApplicationSuite) TestApplicationUnsetExposeEndpoints(c *gc.C) { 2810 // Check that querying for the exposed flag works correctly. 2811 c.Assert(s.mysql.IsExposed(), jc.IsFalse) 2812 2813 // Set initial value 2814 initial := map[string]state.ExposedEndpoint{ 2815 "": { 2816 ExposeToCIDRs: []string{"13.37.0.0/16"}, 2817 }, 2818 "server": { 2819 ExposeToSpaceIDs: []string{network.AlphaSpaceId}, 2820 ExposeToCIDRs: []string{"13.37.0.0/16"}, 2821 }, 2822 } 2823 err := s.mysql.MergeExposeSettings(initial) 2824 c.Assert(err, jc.ErrorIsNil) 2825 c.Assert(s.mysql.ExposedEndpoints(), gc.DeepEquals, initial) 2826 2827 // Check argument validation 2828 err = s.mysql.UnsetExposeSettings([]string{"bogus-endpoint"}) 2829 c.Assert(err, gc.ErrorMatches, `.*endpoint "bogus-endpoint" not found`) 2830 err = s.mysql.UnsetExposeSettings([]string{"server-admin"}) 2831 c.Assert(err, gc.ErrorMatches, `.*endpoint "server-admin" is not exposed`) 2832 2833 // Check unexpose logic 2834 err = s.mysql.UnsetExposeSettings([]string{""}) 2835 c.Assert(err, jc.ErrorIsNil) 2836 c.Assert(s.mysql.ExposedEndpoints(), gc.DeepEquals, map[string]state.ExposedEndpoint{ 2837 "server": { 2838 ExposeToSpaceIDs: []string{network.AlphaSpaceId}, 2839 ExposeToCIDRs: []string{"13.37.0.0/16"}, 2840 }, 2841 }, gc.Commentf("expected the entry of the wildcard endpoint to be removed")) 2842 c.Assert(s.mysql.IsExposed(), jc.IsTrue, gc.Commentf("expected application to remain exposed")) 2843 2844 err = s.mysql.UnsetExposeSettings([]string{"server"}) 2845 c.Assert(err, jc.ErrorIsNil) 2846 c.Assert(s.mysql.ExposedEndpoints(), gc.HasLen, 0) 2847 c.Assert(s.mysql.IsExposed(), jc.IsFalse, gc.Commentf("expected exposed flag to be cleared when last expose setting gets removed")) 2848 } 2849 2850 func (s *ApplicationSuite) TestAddUnit(c *gc.C) { 2851 // Check that principal units can be added on their own. 2852 c.Assert(s.mysql.UnitCount(), gc.Equals, 0) 2853 unitZero, err := s.mysql.AddUnit(state.AddUnitParams{}) 2854 c.Assert(err, jc.ErrorIsNil) 2855 err = s.mysql.Refresh() 2856 c.Assert(err, jc.ErrorIsNil) 2857 c.Assert(s.mysql.UnitCount(), gc.Equals, 1) 2858 c.Assert(unitZero.Name(), gc.Equals, "mysql/0") 2859 c.Assert(unitZero.IsPrincipal(), jc.IsTrue) 2860 c.Assert(unitZero.SubordinateNames(), gc.HasLen, 0) 2861 c.Assert(state.GetUnitModelUUID(unitZero), gc.Equals, s.State.ModelUUID()) 2862 2863 unitOne, err := s.mysql.AddUnit(state.AddUnitParams{}) 2864 c.Assert(err, jc.ErrorIsNil) 2865 c.Assert(unitOne.Name(), gc.Equals, "mysql/1") 2866 c.Assert(unitOne.IsPrincipal(), jc.IsTrue) 2867 c.Assert(unitOne.SubordinateNames(), gc.HasLen, 0) 2868 2869 // Assign the principal unit to a machine. 2870 m, err := s.State.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits) 2871 c.Assert(err, jc.ErrorIsNil) 2872 err = unitZero.AssignToMachine(m) 2873 c.Assert(err, jc.ErrorIsNil) 2874 2875 // Add a subordinate application and check that units cannot be added directly. 2876 // to add a subordinate unit. 2877 subCharm := s.AddTestingCharm(c, "logging") 2878 logging := s.AddTestingApplication(c, "logging", subCharm) 2879 _, err = logging.AddUnit(state.AddUnitParams{}) 2880 c.Assert(err, gc.ErrorMatches, `cannot add unit to application "logging": application is a subordinate`) 2881 2882 // Indirectly create a subordinate unit by adding a relation and entering 2883 // scope as a principal. 2884 eps, err := s.State.InferEndpoints("logging", "mysql") 2885 c.Assert(err, jc.ErrorIsNil) 2886 rel, err := s.State.AddRelation(eps...) 2887 c.Assert(err, jc.ErrorIsNil) 2888 ru, err := rel.Unit(unitZero) 2889 c.Assert(err, jc.ErrorIsNil) 2890 err = ru.EnterScope(nil) 2891 c.Assert(err, jc.ErrorIsNil) 2892 subZero, err := s.State.Unit("logging/0") 2893 c.Assert(err, jc.ErrorIsNil) 2894 2895 // Check that once it's refreshed unitZero has subordinates. 2896 err = unitZero.Refresh() 2897 c.Assert(err, jc.ErrorIsNil) 2898 c.Assert(unitZero.SubordinateNames(), gc.DeepEquals, []string{"logging/0"}) 2899 2900 // Check the subordinate unit has been assigned its principal's machine. 2901 id, err := subZero.AssignedMachineId() 2902 c.Assert(err, jc.ErrorIsNil) 2903 c.Assert(id, gc.Equals, m.Id()) 2904 } 2905 2906 func (s *ApplicationSuite) TestAddUnitWhenNotAlive(c *gc.C) { 2907 u, err := s.mysql.AddUnit(state.AddUnitParams{}) 2908 c.Assert(err, jc.ErrorIsNil) 2909 err = s.mysql.Destroy() 2910 c.Assert(err, jc.ErrorIsNil) 2911 assertLife(c, s.mysql, state.Dying) 2912 _, err = s.mysql.AddUnit(state.AddUnitParams{}) 2913 c.Assert(err, gc.ErrorMatches, `cannot add unit to application "mysql": application is not found or not alive`) 2914 c.Assert(u.EnsureDead(), jc.ErrorIsNil) 2915 c.Assert(u.Remove(), jc.ErrorIsNil) 2916 c.Assert(s.State.Cleanup(), jc.ErrorIsNil) 2917 _, err = s.mysql.AddUnit(state.AddUnitParams{}) 2918 c.Assert(err, gc.ErrorMatches, `cannot add unit to application "mysql": application "mysql" not found`) 2919 } 2920 2921 func (s *ApplicationSuite) TestAddCAASUnit(c *gc.C) { 2922 st := s.Factory.MakeModel(c, &factory.ModelParams{ 2923 Name: "caas-model", 2924 Type: state.ModelTypeCAAS, 2925 }) 2926 defer st.Close() 2927 f := factory.NewFactory(st, s.StatePool) 2928 ch := f.MakeCharm(c, &factory.CharmParams{Name: "gitlab", Series: "kubernetes"}) 2929 app := f.MakeApplication(c, &factory.ApplicationParams{Name: "gitlab", Charm: ch}) 2930 2931 unitZero, err := app.AddUnit(state.AddUnitParams{}) 2932 c.Assert(err, jc.ErrorIsNil) 2933 c.Assert(unitZero.Name(), gc.Equals, "gitlab/0") 2934 c.Assert(unitZero.IsPrincipal(), jc.IsTrue) 2935 c.Assert(unitZero.SubordinateNames(), gc.HasLen, 0) 2936 c.Assert(state.GetUnitModelUUID(unitZero), gc.Equals, st.ModelUUID()) 2937 2938 err = unitZero.SetWorkloadVersion("3.combined") 2939 c.Assert(err, jc.ErrorIsNil) 2940 version, err := unitZero.WorkloadVersion() 2941 c.Assert(err, jc.ErrorIsNil) 2942 c.Check(version, gc.Equals, "3.combined") 2943 2944 err = unitZero.SetMeterStatus(state.MeterGreen.String(), "all good") 2945 c.Assert(err, jc.ErrorIsNil) 2946 ms, err := unitZero.GetMeterStatus() 2947 c.Assert(err, jc.ErrorIsNil) 2948 c.Assert(ms.Code, gc.Equals, state.MeterGreen) 2949 c.Assert(ms.Info, gc.Equals, "all good") 2950 2951 // But they do have status. 2952 us, err := unitZero.Status() 2953 c.Assert(err, jc.ErrorIsNil) 2954 us.Since = nil 2955 c.Assert(us, jc.DeepEquals, status.StatusInfo{ 2956 Status: status.Waiting, 2957 Message: status.MessageInstallingAgent, 2958 Data: map[string]interface{}{}, 2959 }) 2960 as, err := unitZero.AgentStatus() 2961 c.Assert(err, jc.ErrorIsNil) 2962 c.Assert(as.Since, gc.NotNil) 2963 as.Since = nil 2964 c.Assert(as, jc.DeepEquals, status.StatusInfo{ 2965 Status: status.Allocating, 2966 Data: map[string]interface{}{}, 2967 }) 2968 } 2969 2970 func (s *ApplicationSuite) TestAgentTools(c *gc.C) { 2971 st := s.Factory.MakeModel(c, &factory.ModelParams{ 2972 Name: "caas-model", 2973 Type: state.ModelTypeCAAS, 2974 }) 2975 defer st.Close() 2976 f := factory.NewFactory(st, s.StatePool) 2977 ch := f.MakeCharm(c, &factory.CharmParams{Name: "gitlab", Series: "kubernetes"}) 2978 app := f.MakeApplication(c, &factory.ApplicationParams{Charm: ch}) 2979 agentTools := version.Binary{ 2980 Number: jujuversion.Current, 2981 Arch: arch.HostArch(), 2982 Release: "ubuntu", 2983 } 2984 2985 tools, err := app.AgentTools() 2986 c.Assert(err, jc.ErrorIsNil) 2987 c.Assert(tools.Version, gc.DeepEquals, agentTools) 2988 } 2989 2990 func (s *ApplicationSuite) TestSetAgentVersion(c *gc.C) { 2991 st := s.Factory.MakeCAASModel(c, nil) 2992 defer st.Close() 2993 f := factory.NewFactory(st, s.StatePool) 2994 ch := f.MakeCharm(c, &factory.CharmParams{Name: "gitlab", Series: "kubernetes"}) 2995 app := f.MakeApplication(c, &factory.ApplicationParams{Charm: ch}) 2996 2997 agentVersion := version.MustParseBinary("2.0.1-ubuntu-and64") 2998 err := app.SetAgentVersion(agentVersion) 2999 c.Assert(err, jc.ErrorIsNil) 3000 3001 err = app.Refresh() 3002 c.Assert(err, jc.ErrorIsNil) 3003 3004 tools, err := app.AgentTools() 3005 c.Assert(err, jc.ErrorIsNil) 3006 c.Assert(tools.Version, gc.DeepEquals, agentVersion) 3007 } 3008 3009 func (s *ApplicationSuite) TestAddUnitWithProviderIdNonCAASModel(c *gc.C) { 3010 u, err := s.mysql.AddUnit(state.AddUnitParams{ProviderId: strPtr("provider-id")}) 3011 c.Assert(err, jc.ErrorIsNil) 3012 _, err = u.ContainerInfo() 3013 c.Assert(err, jc.Satisfies, errors.IsNotFound) 3014 } 3015 3016 func (s *ApplicationSuite) TestReadUnit(c *gc.C) { 3017 _, err := s.mysql.AddUnit(state.AddUnitParams{}) 3018 c.Assert(err, jc.ErrorIsNil) 3019 _, err = s.mysql.AddUnit(state.AddUnitParams{}) 3020 c.Assert(err, jc.ErrorIsNil) 3021 3022 // Check that retrieving a unit from state works correctly. 3023 unit, err := s.State.Unit("mysql/0") 3024 c.Assert(err, jc.ErrorIsNil) 3025 c.Assert(unit.Name(), gc.Equals, "mysql/0") 3026 3027 // Check that retrieving a non-existent or an invalidly 3028 // named unit fail nicely. 3029 unit, err = s.State.Unit("mysql") 3030 c.Assert(err, gc.ErrorMatches, `"mysql" is not a valid unit name`) 3031 unit, err = s.State.Unit("mysql/0/0") 3032 c.Assert(err, gc.ErrorMatches, `"mysql/0/0" is not a valid unit name`) 3033 unit, err = s.State.Unit("pressword/0") 3034 c.Assert(err, gc.ErrorMatches, `unit "pressword/0" not found`) 3035 3036 units, err := s.mysql.AllUnits() 3037 c.Assert(err, jc.ErrorIsNil) 3038 c.Assert(sortedUnitNames(units), gc.DeepEquals, []string{"mysql/0", "mysql/1"}) 3039 } 3040 3041 func (s *ApplicationSuite) TestReadUnitWhenDying(c *gc.C) { 3042 // Test that we can still read units when the application is Dying... 3043 unit, err := s.mysql.AddUnit(state.AddUnitParams{}) 3044 c.Assert(err, jc.ErrorIsNil) 3045 preventUnitDestroyRemove(c, unit) 3046 err = s.mysql.Destroy() 3047 c.Assert(err, jc.ErrorIsNil) 3048 assertLife(c, s.mysql, state.Dying) 3049 _, err = s.mysql.AllUnits() 3050 c.Assert(err, jc.ErrorIsNil) 3051 _, err = s.State.Unit("mysql/0") 3052 c.Assert(err, jc.ErrorIsNil) 3053 3054 // ...and when those units are Dying or Dead... 3055 testWhenDying(c, unit, noErr, noErr, func() error { 3056 _, err := s.mysql.AllUnits() 3057 return err 3058 }, func() error { 3059 _, err := s.State.Unit("mysql/0") 3060 return err 3061 }) 3062 3063 // ...and even, in a very limited way, when the application itself is removed. 3064 removeAllUnits(c, s.mysql) 3065 _, err = s.mysql.AllUnits() 3066 c.Assert(err, jc.ErrorIsNil) 3067 } 3068 3069 func (s *ApplicationSuite) TestDestroySimple(c *gc.C) { 3070 err := s.mysql.Destroy() 3071 c.Assert(err, jc.ErrorIsNil) 3072 c.Assert(s.mysql.Life(), gc.Equals, state.Dying) 3073 err = s.mysql.Refresh() 3074 c.Assert(err, jc.Satisfies, errors.IsNotFound) 3075 } 3076 3077 func (s *ApplicationSuite) TestDestroyRemovesStatusHistory(c *gc.C) { 3078 err := s.mysql.SetStatus(status.StatusInfo{ 3079 Status: status.Active, 3080 }) 3081 c.Assert(err, jc.ErrorIsNil) 3082 filter := status.StatusHistoryFilter{Size: 100} 3083 agentInfo, err := s.mysql.StatusHistory(filter) 3084 c.Assert(err, jc.ErrorIsNil) 3085 c.Assert(len(agentInfo), gc.Equals, 2) 3086 3087 err = s.mysql.Destroy() 3088 c.Assert(err, jc.ErrorIsNil) 3089 3090 agentInfo, err = s.mysql.StatusHistory(filter) 3091 c.Assert(err, jc.ErrorIsNil) 3092 c.Assert(agentInfo, gc.HasLen, 0) 3093 } 3094 3095 func (s *ApplicationSuite) TestDestroyStillHasUnits(c *gc.C) { 3096 unit, err := s.mysql.AddUnit(state.AddUnitParams{}) 3097 c.Assert(err, jc.ErrorIsNil) 3098 err = s.mysql.Destroy() 3099 c.Assert(err, jc.ErrorIsNil) 3100 c.Assert(s.mysql.Life(), gc.Equals, state.Dying) 3101 3102 c.Assert(unit.EnsureDead(), jc.ErrorIsNil) 3103 c.Assert(s.mysql.Refresh(), jc.ErrorIsNil) 3104 c.Assert(s.mysql.Life(), gc.Equals, state.Dying) 3105 3106 c.Assert(unit.Remove(), jc.ErrorIsNil) 3107 c.Assert(s.State.Cleanup(), jc.ErrorIsNil) 3108 err = s.mysql.Refresh() 3109 c.Assert(err, jc.Satisfies, errors.IsNotFound) 3110 } 3111 3112 func (s *ApplicationSuite) TestDestroyOnceHadUnits(c *gc.C) { 3113 unit, err := s.mysql.AddUnit(state.AddUnitParams{}) 3114 c.Assert(err, jc.ErrorIsNil) 3115 err = unit.EnsureDead() 3116 c.Assert(err, jc.ErrorIsNil) 3117 err = unit.Remove() 3118 c.Assert(err, jc.ErrorIsNil) 3119 3120 err = s.mysql.Destroy() 3121 c.Assert(err, jc.ErrorIsNil) 3122 c.Assert(s.mysql.Life(), gc.Equals, state.Dying) 3123 err = s.mysql.Refresh() 3124 c.Assert(err, jc.Satisfies, errors.IsNotFound) 3125 } 3126 3127 func (s *ApplicationSuite) TestDestroyStaleNonZeroUnitCount(c *gc.C) { 3128 unit, err := s.mysql.AddUnit(state.AddUnitParams{}) 3129 c.Assert(err, jc.ErrorIsNil) 3130 err = s.mysql.Refresh() 3131 c.Assert(err, jc.ErrorIsNil) 3132 err = unit.EnsureDead() 3133 c.Assert(err, jc.ErrorIsNil) 3134 err = unit.Remove() 3135 c.Assert(err, jc.ErrorIsNil) 3136 3137 err = s.mysql.Destroy() 3138 c.Assert(err, jc.ErrorIsNil) 3139 c.Assert(s.mysql.Life(), gc.Equals, state.Dying) 3140 err = s.mysql.Refresh() 3141 c.Assert(err, jc.Satisfies, errors.IsNotFound) 3142 } 3143 3144 func (s *ApplicationSuite) TestDestroyStaleZeroUnitCount(c *gc.C) { 3145 unit, err := s.mysql.AddUnit(state.AddUnitParams{}) 3146 c.Assert(err, jc.ErrorIsNil) 3147 3148 err = s.mysql.Destroy() 3149 c.Assert(err, jc.ErrorIsNil) 3150 c.Assert(s.mysql.Life(), gc.Equals, state.Dying) 3151 3152 err = s.mysql.Refresh() 3153 c.Assert(err, jc.ErrorIsNil) 3154 c.Assert(s.mysql.Life(), gc.Equals, state.Dying) 3155 3156 err = unit.EnsureDead() 3157 c.Assert(err, jc.ErrorIsNil) 3158 err = s.mysql.Refresh() 3159 c.Assert(err, jc.ErrorIsNil) 3160 c.Assert(s.mysql.Life(), gc.Equals, state.Dying) 3161 3162 c.Assert(unit.Remove(), jc.ErrorIsNil) 3163 c.Assert(s.State.Cleanup(), jc.ErrorIsNil) 3164 err = s.mysql.Refresh() 3165 c.Assert(err, jc.Satisfies, errors.IsNotFound) 3166 } 3167 3168 func (s *ApplicationSuite) TestDestroyWithRemovableRelation(c *gc.C) { 3169 wordpress := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 3170 eps, err := s.State.InferEndpoints("wordpress", "mysql") 3171 c.Assert(err, jc.ErrorIsNil) 3172 rel, err := s.State.AddRelation(eps...) 3173 c.Assert(err, jc.ErrorIsNil) 3174 3175 // Destroy a application with no units in relation scope; check application and 3176 // unit removed. 3177 err = wordpress.Destroy() 3178 c.Assert(err, jc.ErrorIsNil) 3179 err = wordpress.Refresh() 3180 c.Assert(err, jc.Satisfies, errors.IsNotFound) 3181 err = rel.Refresh() 3182 c.Assert(err, jc.Satisfies, errors.IsNotFound) 3183 } 3184 3185 func (s *ApplicationSuite) TestDestroyWithRemovableApplicationOpenedPortRanges(c *gc.C) { 3186 st, app := s.addCAASSidecarApplication(c) 3187 defer st.Close() 3188 3189 appPortRanges, err := app.OpenedPortRanges() 3190 c.Assert(err, jc.ErrorIsNil) 3191 c.Assert(appPortRanges.UniquePortRanges(), gc.HasLen, 0) 3192 3193 unit0, err := app.AddUnit(state.AddUnitParams{}) 3194 c.Assert(err, jc.ErrorIsNil) 3195 portRangesUnit0, err := unit0.OpenedPortRanges() 3196 c.Assert(err, jc.ErrorIsNil) 3197 portRangesUnit0.Open(allEndpoints, network.MustParsePortRange("3000/tcp")) 3198 portRangesUnit0.Open(allEndpoints, network.MustParsePortRange("3001/tcp")) 3199 c.Assert(st.ApplyOperation(portRangesUnit0.Changes()), jc.ErrorIsNil) 3200 3201 portRangesUnit0, err = unit0.OpenedPortRanges() 3202 c.Assert(err, jc.ErrorIsNil) 3203 c.Assert(portRangesUnit0.UniquePortRanges(), gc.HasLen, 2) 3204 3205 unit1, err := app.AddUnit(state.AddUnitParams{}) 3206 c.Assert(err, jc.ErrorIsNil) 3207 portRangesUnit1, err := unit1.OpenedPortRanges() 3208 c.Assert(err, jc.ErrorIsNil) 3209 portRangesUnit1.Open(allEndpoints, network.MustParsePortRange("3001/tcp")) 3210 portRangesUnit1.Open(allEndpoints, network.MustParsePortRange("3002/tcp")) 3211 c.Assert(st.ApplyOperation(portRangesUnit1.Changes()), jc.ErrorIsNil) 3212 3213 portRangesUnit1, err = unit1.OpenedPortRanges() 3214 c.Assert(err, jc.ErrorIsNil) 3215 c.Assert(portRangesUnit1.UniquePortRanges(), gc.HasLen, 2) 3216 3217 appPortRanges, err = app.OpenedPortRanges() 3218 c.Assert(err, jc.ErrorIsNil) 3219 c.Assert(appPortRanges.UniquePortRanges(), gc.HasLen, 3) 3220 3221 portRangesUnit1.Close(allEndpoints, network.MustParsePortRange("3002/tcp")) 3222 c.Assert(st.ApplyOperation(portRangesUnit1.Changes()), jc.ErrorIsNil) 3223 3224 portRangesUnit1, err = unit1.OpenedPortRanges() 3225 c.Assert(err, jc.ErrorIsNil) 3226 c.Assert(portRangesUnit1.UniquePortRanges(), gc.HasLen, 1) 3227 3228 appPortRanges, err = app.OpenedPortRanges() 3229 c.Assert(err, jc.ErrorIsNil) 3230 c.Assert(appPortRanges.UniquePortRanges(), gc.HasLen, 2) 3231 3232 portRangesUnit1.Open(allEndpoints, network.MustParsePortRange("3003/tcp")) 3233 c.Assert(st.ApplyOperation(portRangesUnit1.Changes()), jc.ErrorIsNil) 3234 3235 appPortRanges, err = app.OpenedPortRanges() 3236 c.Assert(err, jc.ErrorIsNil) 3237 c.Assert(appPortRanges.UniquePortRanges(), gc.HasLen, 3) 3238 3239 err = unit1.EnsureDead() 3240 c.Assert(err, jc.ErrorIsNil) 3241 err = unit1.Remove() 3242 c.Assert(err, jc.ErrorIsNil) 3243 3244 appPortRanges, err = app.OpenedPortRanges() 3245 c.Assert(err, jc.ErrorIsNil) 3246 c.Assert(appPortRanges.UniquePortRanges(), gc.HasLen, 2) 3247 3248 // Remove all units, all opened ports should be removed. 3249 err = unit0.EnsureDead() 3250 c.Assert(err, jc.ErrorIsNil) 3251 err = unit0.Remove() 3252 c.Assert(err, jc.ErrorIsNil) 3253 err = unit1.EnsureDead() 3254 c.Assert(err, jc.ErrorIsNil) 3255 err = unit1.Remove() 3256 c.Assert(err, jc.ErrorIsNil) 3257 3258 appPortRanges, err = app.OpenedPortRanges() 3259 c.Assert(err, jc.ErrorIsNil) 3260 c.Assert(appPortRanges.UniquePortRanges(), gc.HasLen, 0) 3261 3262 err = app.Destroy() 3263 c.Assert(err, jc.ErrorIsNil) 3264 } 3265 3266 func (s *ApplicationSuite) TestOpenedPortRanges(c *gc.C) { 3267 st, app := s.addCAASSidecarApplication(c) 3268 defer st.Close() 3269 unit, err := app.AddUnit(state.AddUnitParams{}) 3270 c.Assert(err, jc.ErrorIsNil) 3271 portRanges, err := unit.OpenedPortRanges() 3272 c.Assert(err, jc.ErrorIsNil) 3273 3274 flush := func(expectedErr string) { 3275 if len(expectedErr) == 0 { 3276 c.Assert(st.ApplyOperation(portRanges.Changes()), jc.ErrorIsNil) 3277 } else { 3278 c.Assert(st.ApplyOperation(portRanges.Changes()), gc.ErrorMatches, expectedErr) 3279 } 3280 portRanges, err = unit.OpenedPortRanges() 3281 c.Assert(err, jc.ErrorIsNil) 3282 } 3283 3284 c.Assert(portRanges.UniquePortRanges(), gc.HasLen, 0) 3285 portRanges.Open(allEndpoints, network.MustParsePortRange("3000/tcp")) 3286 portRanges.Open("data-port", network.MustParsePortRange("2000/udp")) 3287 // All good. 3288 flush(``) 3289 c.Assert(portRanges.UnitName(), jc.DeepEquals, `cockroachdb/0`) 3290 c.Assert(portRanges.UniquePortRanges(), jc.DeepEquals, []network.PortRange{ 3291 network.MustParsePortRange("3000/tcp"), 3292 network.MustParsePortRange("2000/udp"), 3293 }) 3294 c.Assert(portRanges.ByEndpoint(), jc.DeepEquals, network.GroupedPortRanges{ 3295 allEndpoints: []network.PortRange{network.MustParsePortRange("3000/tcp")}, 3296 "data-port": []network.PortRange{network.MustParsePortRange("2000/udp")}, 3297 }) 3298 3299 // Errors for unknown endpoint. 3300 portRanges.Open("bad-endpoint", network.MustParsePortRange("2000/udp")) 3301 flush(`cannot open/close ports: open port range: endpoint "bad-endpoint" for application "cockroachdb" not found`) 3302 c.Assert(portRanges.ByEndpoint(), jc.DeepEquals, network.GroupedPortRanges{ 3303 allEndpoints: []network.PortRange{network.MustParsePortRange("3000/tcp")}, 3304 "data-port": []network.PortRange{network.MustParsePortRange("2000/udp")}, 3305 }) 3306 3307 // No ops for duplicated Open. 3308 portRanges.Open("data-port", network.MustParsePortRange("2000/udp")) 3309 flush(``) 3310 c.Assert(portRanges.ByEndpoint(), jc.DeepEquals, network.GroupedPortRanges{ 3311 allEndpoints: []network.PortRange{network.MustParsePortRange("3000/tcp")}, 3312 "data-port": []network.PortRange{network.MustParsePortRange("2000/udp")}, 3313 }) 3314 3315 // Close one port. 3316 portRanges.Close("data-port", network.MustParsePortRange("2000/udp")) 3317 flush(``) 3318 c.Assert(portRanges.ByEndpoint(), jc.DeepEquals, network.GroupedPortRanges{ 3319 allEndpoints: []network.PortRange{network.MustParsePortRange("3000/tcp")}, 3320 }) 3321 3322 // No ops for Close non existing port. 3323 portRanges.Close("data-port", network.MustParsePortRange("2000/udp")) 3324 flush(``) 3325 c.Assert(portRanges.ByEndpoint(), jc.DeepEquals, network.GroupedPortRanges{ 3326 allEndpoints: []network.PortRange{network.MustParsePortRange("3000/tcp")}, 3327 }) 3328 3329 // Destroy the application; check application and 3330 // openedApplicationportRanges removed. 3331 err = unit.EnsureDead() 3332 c.Assert(err, jc.ErrorIsNil) 3333 err = unit.Remove() 3334 c.Assert(err, jc.ErrorIsNil) 3335 3336 appPortRanges, err := app.OpenedPortRanges() 3337 c.Assert(err, jc.ErrorIsNil) 3338 c.Assert(appPortRanges.UniquePortRanges(), gc.HasLen, 0) 3339 3340 err = app.Destroy() 3341 c.Assert(err, jc.ErrorIsNil) 3342 } 3343 3344 func (s *ApplicationSuite) TestDestroyWithReferencedRelation(c *gc.C) { 3345 s.assertDestroyWithReferencedRelation(c, true) 3346 } 3347 3348 func (s *ApplicationSuite) TestDestroyWithReferencedRelationStaleCount(c *gc.C) { 3349 s.assertDestroyWithReferencedRelation(c, false) 3350 } 3351 3352 func (s *ApplicationSuite) assertDestroyWithReferencedRelation(c *gc.C, refresh bool) { 3353 wordpress := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 3354 eps, err := s.State.InferEndpoints("wordpress", "mysql") 3355 c.Assert(err, jc.ErrorIsNil) 3356 rel0, err := s.State.AddRelation(eps...) 3357 c.Assert(err, jc.ErrorIsNil) 3358 3359 s.AddTestingApplication(c, "logging", s.AddTestingCharm(c, "logging")) 3360 eps, err = s.State.InferEndpoints("logging", "mysql") 3361 c.Assert(err, jc.ErrorIsNil) 3362 rel1, err := s.State.AddRelation(eps...) 3363 c.Assert(err, jc.ErrorIsNil) 3364 3365 // Add a separate reference to the first relation. 3366 unit, err := wordpress.AddUnit(state.AddUnitParams{}) 3367 c.Assert(err, jc.ErrorIsNil) 3368 ru, err := rel0.Unit(unit) 3369 c.Assert(err, jc.ErrorIsNil) 3370 err = ru.EnterScope(nil) 3371 c.Assert(err, jc.ErrorIsNil) 3372 3373 // Optionally update the application document to get correct relation counts. 3374 if refresh { 3375 err = s.mysql.Destroy() 3376 c.Assert(err, jc.ErrorIsNil) 3377 } 3378 3379 // Destroy, and check that the first relation becomes Dying... 3380 c.Assert(s.mysql.Destroy(), jc.ErrorIsNil) 3381 err = rel0.Refresh() 3382 c.Assert(err, jc.ErrorIsNil) 3383 c.Assert(rel0.Life(), gc.Equals, state.Dying) 3384 3385 // ...while the second is removed directly. 3386 err = rel1.Refresh() 3387 c.Assert(err, jc.Satisfies, errors.IsNotFound) 3388 3389 // Drop the last reference to the first relation; check the relation and 3390 // the application are are both removed. 3391 c.Assert(ru.LeaveScope(), jc.ErrorIsNil) 3392 c.Assert(s.State.Cleanup(), jc.ErrorIsNil) 3393 err = s.mysql.Refresh() 3394 c.Assert(err, jc.Satisfies, errors.IsNotFound) 3395 err = rel0.Refresh() 3396 c.Assert(err, jc.Satisfies, errors.IsNotFound) 3397 } 3398 3399 func (s *ApplicationSuite) TestDestroyQueuesUnitCleanup(c *gc.C) { 3400 // Add 5 units; block quick-remove of mysql/1 and mysql/3 3401 units := make([]*state.Unit, 5) 3402 for i := range units { 3403 unit, err := s.mysql.AddUnit(state.AddUnitParams{}) 3404 c.Assert(err, jc.ErrorIsNil) 3405 units[i] = unit 3406 if i%2 != 0 { 3407 preventUnitDestroyRemove(c, unit) 3408 } 3409 } 3410 3411 s.assertNoCleanup(c) 3412 3413 // Destroy mysql, and check units are not touched. 3414 err := s.mysql.Destroy() 3415 c.Assert(err, jc.ErrorIsNil) 3416 assertLife(c, s.mysql, state.Dying) 3417 for _, unit := range units { 3418 assertLife(c, unit, state.Alive) 3419 } 3420 3421 s.assertNeedsCleanup(c) 3422 3423 // Run the cleanup and check the units. 3424 err = s.State.Cleanup() 3425 c.Assert(err, jc.ErrorIsNil) 3426 for i, unit := range units { 3427 if i%2 != 0 { 3428 assertLife(c, unit, state.Dying) 3429 } else { 3430 assertRemoved(c, unit) 3431 } 3432 } 3433 3434 // Check for queued unit cleanups, and run them. 3435 s.assertNeedsCleanup(c) 3436 err = s.State.Cleanup() 3437 c.Assert(err, jc.ErrorIsNil) 3438 3439 // Check we're now clean. 3440 s.assertNoCleanup(c) 3441 } 3442 3443 func (s *ApplicationSuite) TestRemoveApplicationMachine(c *gc.C) { 3444 unit, err := s.mysql.AddUnit(state.AddUnitParams{}) 3445 c.Assert(err, jc.ErrorIsNil) 3446 machine, err := s.State.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits) 3447 c.Assert(err, jc.ErrorIsNil) 3448 c.Assert(unit.AssignToMachine(machine), gc.IsNil) 3449 3450 c.Assert(s.mysql.Destroy(), gc.IsNil) 3451 assertLife(c, s.mysql, state.Dying) 3452 3453 // Application.Destroy adds units to cleanup, make it happen now. 3454 c.Assert(s.State.Cleanup(), gc.IsNil) 3455 3456 c.Assert(unit.Refresh(), jc.Satisfies, errors.IsNotFound) 3457 assertLife(c, machine, state.Dying) 3458 } 3459 3460 func (s *ApplicationSuite) TestDestroyRemoveAlsoDeletesSecretPermissions(c *gc.C) { 3461 store := state.NewSecrets(s.State) 3462 uri := secrets.NewURI() 3463 cp := state.CreateSecretParams{ 3464 Version: 1, 3465 Owner: s.mysql.Tag(), 3466 UpdateSecretParams: state.UpdateSecretParams{ 3467 LeaderToken: &fakeToken{}, 3468 Data: map[string]string{"foo": "bar"}, 3469 }, 3470 } 3471 _, err := store.CreateSecret(uri, cp) 3472 c.Assert(err, jc.ErrorIsNil) 3473 3474 // Make a relation for the access scope. 3475 endpoint1, err := s.mysql.Endpoint("juju-info") 3476 c.Assert(err, jc.ErrorIsNil) 3477 application2 := s.Factory.MakeApplication(c, &factory.ApplicationParams{ 3478 Charm: s.Factory.MakeCharm(c, &factory.CharmParams{ 3479 Name: "logging", 3480 }), 3481 }) 3482 endpoint2, err := application2.Endpoint("info") 3483 c.Assert(err, jc.ErrorIsNil) 3484 rel := s.Factory.MakeRelation(c, &factory.RelationParams{ 3485 Endpoints: []state.Endpoint{endpoint1, endpoint2}, 3486 }) 3487 3488 err = s.State.GrantSecretAccess(uri, state.SecretAccessParams{ 3489 LeaderToken: &fakeToken{}, 3490 Scope: rel.Tag(), 3491 Subject: s.mysql.Tag(), 3492 Role: secrets.RoleView, 3493 }) 3494 c.Assert(err, jc.ErrorIsNil) 3495 access, err := s.State.SecretAccess(uri, s.mysql.Tag()) 3496 c.Assert(err, jc.ErrorIsNil) 3497 c.Assert(access, gc.Equals, secrets.RoleView) 3498 3499 err = s.mysql.Destroy() 3500 c.Assert(err, jc.ErrorIsNil) 3501 _, err = s.State.SecretAccess(uri, s.mysql.Tag()) 3502 c.Assert(err, jc.Satisfies, errors.IsNotFound) 3503 } 3504 3505 func (s *ApplicationSuite) TestDestroyRemoveAlsoDeletesOwnedSecrets(c *gc.C) { 3506 store := state.NewSecrets(s.State) 3507 uri := secrets.NewURI() 3508 cp := state.CreateSecretParams{ 3509 Version: 1, 3510 Owner: s.mysql.Tag(), 3511 UpdateSecretParams: state.UpdateSecretParams{ 3512 LeaderToken: &fakeToken{}, 3513 Label: ptr("label"), 3514 Data: map[string]string{"foo": "bar"}, 3515 }, 3516 } 3517 _, err := store.CreateSecret(uri, cp) 3518 c.Assert(err, jc.ErrorIsNil) 3519 3520 err = s.mysql.Destroy() 3521 c.Assert(err, jc.ErrorIsNil) 3522 _, err = store.GetSecret(uri) 3523 c.Assert(err, jc.Satisfies, errors.IsNotFound) 3524 3525 // Create again, no label clash. 3526 s.AddTestingApplication(c, "mysql", s.charm) 3527 _, err = store.CreateSecret(uri, cp) 3528 c.Assert(err, jc.ErrorIsNil) 3529 } 3530 3531 func (s *ApplicationSuite) TestDestroyNoRemoveKeepsOwnedSecrets(c *gc.C) { 3532 // Create a relation so destroy does not remove. 3533 _, err := s.mysql.AddUnit(state.AddUnitParams{}) 3534 c.Assert(err, jc.ErrorIsNil) 3535 mysqlep, err := s.mysql.Endpoint("server") 3536 c.Assert(err, jc.ErrorIsNil) 3537 wpch := s.AddTestingCharm(c, "wordpress") 3538 wp := s.AddTestingApplication(c, "wordpress", wpch) 3539 wpep, err := wp.Endpoint("db") 3540 c.Assert(err, jc.ErrorIsNil) 3541 _, err = s.State.AddRelation(mysqlep, wpep) 3542 c.Assert(err, jc.ErrorIsNil) 3543 3544 store := state.NewSecrets(s.State) 3545 uri := secrets.NewURI() 3546 cp := state.CreateSecretParams{ 3547 Version: 1, 3548 Owner: s.mysql.Tag(), 3549 UpdateSecretParams: state.UpdateSecretParams{ 3550 LeaderToken: &fakeToken{}, 3551 Label: ptr("label"), 3552 Data: map[string]string{"foo": "bar"}, 3553 }, 3554 } 3555 _, err = store.CreateSecret(uri, cp) 3556 c.Assert(err, jc.ErrorIsNil) 3557 3558 err = s.mysql.Destroy() 3559 c.Assert(err, jc.ErrorIsNil) 3560 _, err = store.GetSecret(uri) 3561 c.Assert(err, jc.ErrorIsNil) 3562 } 3563 3564 func (s *ApplicationSuite) TestApplicationCleanupRemovesStorageConstraints(c *gc.C) { 3565 ch := s.AddTestingCharm(c, "storage-block") 3566 storage := map[string]state.StorageConstraints{ 3567 "data": makeStorageCons("loop", 1024, 1), 3568 } 3569 app := s.AddTestingApplicationWithStorage(c, "storage-block", ch, storage) 3570 u, err := app.AddUnit(state.AddUnitParams{}) 3571 c.Assert(err, jc.ErrorIsNil) 3572 err = u.SetCharmURL(ch.URL()) 3573 c.Assert(err, jc.ErrorIsNil) 3574 3575 c.Assert(app.Destroy(), gc.IsNil) 3576 assertLife(c, app, state.Dying) 3577 assertCleanupCount(c, s.State, 2) 3578 3579 // These next API calls are normally done by the uniter. 3580 c.Assert(u.EnsureDead(), jc.ErrorIsNil) 3581 c.Assert(u.Remove(), jc.ErrorIsNil) 3582 c.Assert(s.State.Cleanup(), jc.ErrorIsNil) 3583 3584 // Ensure storage constraints and settings are now gone. 3585 _, err = state.AppStorageConstraints(app) 3586 c.Assert(err, jc.Satisfies, errors.IsNotFound) 3587 cfg := state.GetApplicationCharmConfig(s.State, app) 3588 err = cfg.Read() 3589 c.Assert(err, jc.Satisfies, errors.IsNotFound) 3590 } 3591 3592 func (s *ApplicationSuite) TestApplicationCleanupRemovesAppFromActiveBranches(c *gc.C) { 3593 s.assertNoCleanup(c) 3594 3595 // setup branch, tracking and app with config changes. 3596 app := s.AddTestingApplication(c, "dummy", s.AddTestingCharm(c, "dummy")) 3597 c.Assert(s.Model.AddBranch("apple", "testuser"), jc.ErrorIsNil) 3598 branch, err := s.Model.Branch("apple") 3599 c.Assert(err, jc.ErrorIsNil) 3600 c.Assert(branch.AssignApplication(app.Name()), jc.ErrorIsNil) 3601 c.Assert(branch.AssignApplication(s.mysql.Name()), jc.ErrorIsNil) 3602 newCfg := map[string]interface{}{"outlook": "testing"} 3603 c.Assert(app.UpdateCharmConfig(branch.BranchName(), newCfg), jc.ErrorIsNil) 3604 3605 // verify the branch setup 3606 c.Assert(branch.Refresh(), jc.ErrorIsNil) 3607 c.Assert(branch.AssignedUnits(), jc.DeepEquals, map[string][]string{ 3608 app.Name(): {}, 3609 s.mysql.Name(): {}, 3610 }) 3611 branchCfg := branch.Config() 3612 _, ok := branchCfg[app.Name()] 3613 c.Assert(ok, jc.IsTrue) 3614 3615 // destroy the app 3616 c.Assert(app.Destroy(), gc.IsNil) 3617 assertRemoved(c, app) 3618 3619 // Check the branch 3620 c.Assert(branch.Refresh(), jc.ErrorIsNil) 3621 c.Assert(branch.AssignedUnits(), jc.DeepEquals, map[string][]string{ 3622 s.mysql.Name(): {}, 3623 }) 3624 c.Assert(branch.Config(), gc.HasLen, 0) 3625 } 3626 3627 func (s *ApplicationSuite) TestRemoveQueuesLocalCharmCleanup(c *gc.C) { 3628 s.assertNoCleanup(c) 3629 3630 err := s.mysql.Destroy() 3631 c.Assert(err, jc.ErrorIsNil) 3632 assertRemoved(c, s.mysql) 3633 3634 // Check a cleanup doc was added. 3635 s.assertNeedsCleanup(c) 3636 3637 // Run the cleanup 3638 err = s.State.Cleanup() 3639 c.Assert(err, jc.ErrorIsNil) 3640 3641 // Check charm removed 3642 err = s.charm.Refresh() 3643 c.Check(err, jc.Satisfies, errors.IsNotFound) 3644 3645 // Check we're now clean. 3646 s.assertNoCleanup(c) 3647 } 3648 3649 func (s *ApplicationSuite) TestDestroyQueuesResourcesCleanup(c *gc.C) { 3650 s.assertNoCleanup(c) 3651 3652 // Add a resource to the application, ensuring it is stored. 3653 rSt := s.State.Resources() 3654 const content = "abc" 3655 res := resourcetesting.NewCharmResource(c, "blob", content) 3656 outRes, err := rSt.SetResource(s.mysql.Name(), "user", res, strings.NewReader(content), state.IncrementCharmModifiedVersion) 3657 c.Assert(err, jc.ErrorIsNil) 3658 storagePath := state.ResourceStoragePath(c, s.State, outRes.ID) 3659 c.Assert(state.IsBlobStored(c, s.State, storagePath), jc.IsTrue) 3660 3661 // Detroy the application. 3662 err = s.mysql.Destroy() 3663 c.Assert(err, jc.ErrorIsNil) 3664 assertRemoved(c, s.mysql) 3665 3666 // Cleanup should be registered but not yet run. 3667 s.assertNeedsCleanup(c) 3668 c.Assert(state.IsBlobStored(c, s.State, storagePath), jc.IsTrue) 3669 3670 // Run the cleanup. 3671 err = s.State.Cleanup() 3672 c.Assert(err, jc.ErrorIsNil) 3673 3674 // Check we're now clean. 3675 s.assertNoCleanup(c) 3676 c.Assert(state.IsBlobStored(c, s.State, storagePath), jc.IsFalse) 3677 } 3678 3679 func (s *ApplicationSuite) TestDestroyWithPlaceholderResources(c *gc.C) { 3680 s.assertNoCleanup(c) 3681 3682 // Add a placeholder resource to the application. 3683 rSt := s.State.Resources() 3684 res := resourcetesting.NewPlaceholderResource(c, "blob", s.mysql.Name()) 3685 outRes, err := rSt.SetResource(s.mysql.Name(), "user", res.Resource, nil, state.IncrementCharmModifiedVersion) 3686 c.Assert(err, jc.ErrorIsNil) 3687 c.Assert(outRes.IsPlaceholder(), jc.IsTrue) 3688 3689 // Detroy the application. 3690 err = s.mysql.Destroy() 3691 c.Assert(err, jc.ErrorIsNil) 3692 assertRemoved(c, s.mysql) 3693 3694 // No cleanup required for placeholder resources. 3695 state.AssertNoCleanupsWithKind(c, s.State, "resourceBlob") 3696 } 3697 3698 func (s *ApplicationSuite) TestReadUnitWithChangingState(c *gc.C) { 3699 // Check that reading a unit after removing the application 3700 // fails nicely. 3701 err := s.mysql.Destroy() 3702 c.Assert(err, jc.ErrorIsNil) 3703 assertRemoved(c, s.mysql) 3704 _, err = s.State.Unit("mysql/0") 3705 c.Assert(err, gc.ErrorMatches, `unit "mysql/0" not found`) 3706 } 3707 3708 func uint64p(val uint64) *uint64 { 3709 return &val 3710 } 3711 3712 func (s *ApplicationSuite) TestConstraints(c *gc.C) { 3713 // Constraints are initially empty (for now). 3714 cons, err := s.mysql.Constraints() 3715 c.Assert(err, jc.ErrorIsNil) 3716 c.Assert(&cons, gc.Not(jc.Satisfies), constraints.IsEmpty) 3717 c.Assert(cons, gc.DeepEquals, constraints.MustParse("arch=amd64")) 3718 3719 // Constraints can be set. 3720 cons2 := constraints.Value{Mem: uint64p(4096)} 3721 err = s.mysql.SetConstraints(cons2) 3722 c.Assert(err, jc.ErrorIsNil) 3723 cons3, err := s.mysql.Constraints() 3724 c.Assert(err, jc.ErrorIsNil) 3725 c.Assert(cons3, gc.DeepEquals, cons2) 3726 3727 // Constraints are completely overwritten when re-set. 3728 cons4 := constraints.Value{CpuPower: uint64p(750)} 3729 err = s.mysql.SetConstraints(cons4) 3730 c.Assert(err, jc.ErrorIsNil) 3731 cons5, err := s.mysql.Constraints() 3732 c.Assert(err, jc.ErrorIsNil) 3733 c.Assert(cons5, gc.DeepEquals, cons4) 3734 3735 // Destroy the existing application; there's no way to directly assert 3736 // that the constraints are deleted... 3737 err = s.mysql.Destroy() 3738 c.Assert(err, jc.ErrorIsNil) 3739 assertRemoved(c, s.mysql) 3740 3741 // ...but we can check that old constraints do not affect new applications 3742 // with matching names. 3743 ch, _, err := s.mysql.Charm() 3744 c.Assert(err, jc.ErrorIsNil) 3745 mysql := s.AddTestingApplication(c, s.mysql.Name(), ch) 3746 cons6, err := mysql.Constraints() 3747 c.Assert(err, jc.ErrorIsNil) 3748 c.Assert(&cons6, gc.Not(jc.Satisfies), constraints.IsEmpty) 3749 c.Assert(cons6, gc.DeepEquals, constraints.MustParse("arch=amd64")) 3750 } 3751 3752 func (s *ApplicationSuite) TestArchConstraints(c *gc.C) { 3753 amdArch := "amd64" 3754 armArch := "arm64" 3755 3756 cons2 := constraints.Value{Arch: &amdArch} 3757 err := s.mysql.SetConstraints(cons2) 3758 c.Assert(err, jc.ErrorIsNil) 3759 cons3, err := s.mysql.Constraints() 3760 c.Assert(err, jc.ErrorIsNil) 3761 c.Assert(cons3, gc.DeepEquals, cons2) 3762 3763 // Constraints error out if it's already set. 3764 cons4 := constraints.Value{Arch: &armArch} 3765 err = s.mysql.SetConstraints(cons4) 3766 c.Assert(err, gc.ErrorMatches, "changing architecture \\(amd64\\) not supported") 3767 3768 // Destroy the existing application; there's no way to directly assert 3769 // that the constraints are deleted... 3770 err = s.mysql.Destroy() 3771 c.Assert(err, jc.ErrorIsNil) 3772 assertRemoved(c, s.mysql) 3773 3774 // ...but we can check that old constraints do not affect new applications 3775 // with matching names. 3776 ch, _, err := s.mysql.Charm() 3777 c.Assert(err, jc.ErrorIsNil) 3778 mysql := s.AddTestingApplication(c, s.mysql.Name(), ch) 3779 cons6, err := mysql.Constraints() 3780 c.Assert(err, jc.ErrorIsNil) 3781 c.Assert(constraints.IsEmpty(&cons6), jc.IsFalse) 3782 c.Assert(cons6, jc.DeepEquals, cons2) 3783 } 3784 3785 func (s *ApplicationSuite) TestSetInvalidConstraints(c *gc.C) { 3786 cons := constraints.MustParse("mem=4G instance-type=foo") 3787 err := s.mysql.SetConstraints(cons) 3788 c.Assert(err, gc.ErrorMatches, `ambiguous constraints: "instance-type" overlaps with "mem"`) 3789 } 3790 3791 func (s *ApplicationSuite) TestSetUnsupportedConstraintsWarning(c *gc.C) { 3792 defer loggo.ResetWriters() 3793 logger := loggo.GetLogger("test") 3794 logger.SetLogLevel(loggo.DEBUG) 3795 var tw loggo.TestWriter 3796 c.Assert(loggo.RegisterWriter("constraints-tester", &tw), gc.IsNil) 3797 3798 cons := constraints.MustParse("mem=4G cpu-power=10") 3799 err := s.mysql.SetConstraints(cons) 3800 c.Assert(err, jc.ErrorIsNil) 3801 c.Assert(tw.Log(), jc.LogMatches, jc.SimpleMessages{{ 3802 loggo.WARNING, 3803 `setting constraints on application "mysql": unsupported constraints: cpu-power`}, 3804 }) 3805 scons, err := s.mysql.Constraints() 3806 c.Assert(err, jc.ErrorIsNil) 3807 c.Assert(scons, gc.DeepEquals, cons) 3808 } 3809 3810 func (s *ApplicationSuite) TestConstraintsLifecycle(c *gc.C) { 3811 // Dying. 3812 unit, err := s.mysql.AddUnit(state.AddUnitParams{}) 3813 c.Assert(err, jc.ErrorIsNil) 3814 err = s.mysql.Destroy() 3815 c.Assert(err, jc.ErrorIsNil) 3816 assertLife(c, s.mysql, state.Dying) 3817 3818 cons1 := constraints.MustParse("mem=1G") 3819 err = s.mysql.SetConstraints(cons1) 3820 c.Assert(err, gc.ErrorMatches, `cannot set constraints: application is not found or not alive`) 3821 3822 scons, err := s.mysql.Constraints() 3823 c.Assert(err, jc.ErrorIsNil) 3824 c.Assert(&scons, gc.Not(jc.Satisfies), constraints.IsEmpty) 3825 c.Assert(scons, gc.DeepEquals, constraints.MustParse("arch=amd64")) 3826 3827 // Removed (== Dead, for a application). 3828 c.Assert(unit.EnsureDead(), jc.ErrorIsNil) 3829 c.Assert(unit.Remove(), jc.ErrorIsNil) 3830 c.Assert(s.State.Cleanup(), jc.ErrorIsNil) 3831 err = s.mysql.SetConstraints(cons1) 3832 c.Assert(err, gc.ErrorMatches, `cannot set constraints: application is not found or not alive`) 3833 _, err = s.mysql.Constraints() 3834 c.Assert(err, gc.ErrorMatches, `constraints not found`) 3835 } 3836 3837 func (s *ApplicationSuite) TestSubordinateConstraints(c *gc.C) { 3838 loggingCh := s.AddTestingCharm(c, "logging") 3839 logging := s.AddTestingApplication(c, "logging", loggingCh) 3840 3841 _, err := logging.Constraints() 3842 c.Assert(err, gc.Equals, state.ErrSubordinateConstraints) 3843 3844 err = logging.SetConstraints(constraints.Value{}) 3845 c.Assert(err, gc.Equals, state.ErrSubordinateConstraints) 3846 } 3847 3848 func (s *ApplicationSuite) TestWatchUnitsBulkEvents(c *gc.C) { 3849 // Alive unit... 3850 alive, err := s.mysql.AddUnit(state.AddUnitParams{}) 3851 c.Assert(err, jc.ErrorIsNil) 3852 3853 // Dying unit... 3854 dying, err := s.mysql.AddUnit(state.AddUnitParams{}) 3855 c.Assert(err, jc.ErrorIsNil) 3856 preventUnitDestroyRemove(c, dying) 3857 err = dying.Destroy() 3858 c.Assert(err, jc.ErrorIsNil) 3859 3860 // Dead unit... 3861 dead, err := s.mysql.AddUnit(state.AddUnitParams{}) 3862 c.Assert(err, jc.ErrorIsNil) 3863 preventUnitDestroyRemove(c, dead) 3864 err = dead.Destroy() 3865 c.Assert(err, jc.ErrorIsNil) 3866 err = dead.EnsureDead() 3867 c.Assert(err, jc.ErrorIsNil) 3868 3869 // Gone unit. 3870 gone, err := s.mysql.AddUnit(state.AddUnitParams{}) 3871 c.Assert(err, jc.ErrorIsNil) 3872 err = gone.Destroy() 3873 c.Assert(err, jc.ErrorIsNil) 3874 3875 // All except gone unit are reported in initial event. 3876 w := s.mysql.WatchUnits() 3877 defer testing.AssertStop(c, w) 3878 wc := testing.NewStringsWatcherC(c, w) 3879 wc.AssertChange(alive.Name(), dying.Name(), dead.Name()) 3880 wc.AssertNoChange() 3881 3882 // Remove them all; alive/dying changes reported; dead never mentioned again. 3883 err = alive.Destroy() 3884 c.Assert(err, jc.ErrorIsNil) 3885 err = dying.EnsureDead() 3886 c.Assert(err, jc.ErrorIsNil) 3887 err = dying.Remove() 3888 c.Assert(err, jc.ErrorIsNil) 3889 err = dead.Remove() 3890 c.Assert(err, jc.ErrorIsNil) 3891 wc.AssertChange(alive.Name(), dying.Name()) 3892 wc.AssertNoChange() 3893 } 3894 3895 func (s *ApplicationSuite) TestWatchUnitsLifecycle(c *gc.C) { 3896 // Empty initial event when no units. 3897 w := s.mysql.WatchUnits() 3898 defer testing.AssertStop(c, w) 3899 wc := testing.NewStringsWatcherC(c, w) 3900 wc.AssertChange() 3901 wc.AssertNoChange() 3902 3903 // Create one unit, check one change. 3904 quick, err := s.mysql.AddUnit(state.AddUnitParams{}) 3905 c.Assert(err, jc.ErrorIsNil) 3906 wc.AssertChange(quick.Name()) 3907 wc.AssertNoChange() 3908 3909 // Destroy that unit (short-circuited to removal), check one change. 3910 err = quick.Destroy() 3911 c.Assert(err, jc.ErrorIsNil) 3912 wc.AssertChange(quick.Name()) 3913 wc.AssertNoChange() 3914 3915 // Create another, check one change. 3916 slow, err := s.mysql.AddUnit(state.AddUnitParams{}) 3917 c.Assert(err, jc.ErrorIsNil) 3918 wc.AssertChange(slow.Name()) 3919 wc.AssertNoChange() 3920 3921 // Change unit itself, no change. 3922 preventUnitDestroyRemove(c, slow) 3923 wc.AssertNoChange() 3924 3925 // Make unit Dying, change detected. 3926 err = slow.Destroy() 3927 c.Assert(err, jc.ErrorIsNil) 3928 wc.AssertChange(slow.Name()) 3929 wc.AssertNoChange() 3930 3931 // Make unit Dead, change detected. 3932 err = slow.EnsureDead() 3933 c.Assert(err, jc.ErrorIsNil) 3934 wc.AssertChange(slow.Name()) 3935 wc.AssertNoChange() 3936 3937 // Remove unit, final change not detected. 3938 err = slow.Remove() 3939 c.Assert(err, jc.ErrorIsNil) 3940 wc.AssertNoChange() 3941 } 3942 3943 func (s *ApplicationSuite) TestWatchRelations(c *gc.C) { 3944 // TODO(fwereade) split this test up a bit. 3945 w := s.mysql.WatchRelations() 3946 defer testing.AssertStop(c, w) 3947 wc := testing.NewStringsWatcherC(c, w) 3948 wc.AssertChange() 3949 wc.AssertNoChange() 3950 3951 // Add a relation; check change. 3952 mysqlep, err := s.mysql.Endpoint("server") 3953 c.Assert(err, jc.ErrorIsNil) 3954 wpch := s.AddTestingCharm(c, "wordpress") 3955 wpi := 0 3956 addRelation := func() *state.Relation { 3957 name := fmt.Sprintf("wp%d", wpi) 3958 wpi++ 3959 wp := s.AddTestingApplication(c, name, wpch) 3960 wpep, err := wp.Endpoint("db") 3961 c.Assert(err, jc.ErrorIsNil) 3962 rel, err := s.State.AddRelation(mysqlep, wpep) 3963 c.Assert(err, jc.ErrorIsNil) 3964 return rel 3965 } 3966 rel0 := addRelation() 3967 wc.AssertChange(rel0.String()) 3968 wc.AssertNoChange() 3969 3970 // Add another relation; check change. 3971 rel1 := addRelation() 3972 wc.AssertChange(rel1.String()) 3973 wc.AssertNoChange() 3974 3975 // Destroy a relation; check change. 3976 err = rel0.Destroy() 3977 c.Assert(err, jc.ErrorIsNil) 3978 wc.AssertChange(rel0.String()) 3979 wc.AssertNoChange() 3980 3981 // Stop watcher; check change chan is closed. 3982 testing.AssertStop(c, w) 3983 wc.AssertClosed() 3984 3985 // Add a new relation; start a new watcher; check initial event. 3986 rel2 := addRelation() 3987 w = s.mysql.WatchRelations() 3988 defer testing.AssertStop(c, w) 3989 wc = testing.NewStringsWatcherC(c, w) 3990 wc.AssertChange(rel1.String(), rel2.String()) 3991 wc.AssertNoChange() 3992 3993 // Add a unit to the new relation; check no change. 3994 unit, err := s.mysql.AddUnit(state.AddUnitParams{}) 3995 c.Assert(err, jc.ErrorIsNil) 3996 ru2, err := rel2.Unit(unit) 3997 c.Assert(err, jc.ErrorIsNil) 3998 err = ru2.EnterScope(nil) 3999 c.Assert(err, jc.ErrorIsNil) 4000 wc.AssertNoChange() 4001 4002 // Destroy the relation with the unit in scope, and add another; check 4003 // changes. 4004 err = rel2.Refresh() 4005 c.Assert(err, jc.ErrorIsNil) 4006 err = rel2.Destroy() 4007 c.Assert(err, jc.ErrorIsNil) 4008 rel3 := addRelation() 4009 wc.AssertChange(rel2.String(), rel3.String()) 4010 wc.AssertNoChange() 4011 4012 // Leave scope, destroying the relation, and check that change as well. 4013 err = ru2.LeaveScope() 4014 c.Assert(err, jc.ErrorIsNil) 4015 wc.AssertChange(rel2.String()) 4016 wc.AssertNoChange() 4017 4018 // Watch relations on the requirer application too (exercises a 4019 // different path of the WatchRelations filter function) 4020 wpx := s.AddTestingApplication(c, "wpx", wpch) 4021 wpxWatcher := wpx.WatchRelations() 4022 defer testing.AssertStop(c, wpxWatcher) 4023 wpxWatcherC := testing.NewStringsWatcherC(c, wpxWatcher) 4024 wpxWatcherC.AssertChange() 4025 wpxWatcherC.AssertNoChange() 4026 4027 wpxep, err := wpx.Endpoint("db") 4028 c.Assert(err, jc.ErrorIsNil) 4029 relx, err := s.State.AddRelation(mysqlep, wpxep) 4030 c.Assert(err, jc.ErrorIsNil) 4031 wpxWatcherC.AssertChange(relx.String()) 4032 wpxWatcherC.AssertNoChange() 4033 4034 err = relx.SetSuspended(true, "") 4035 c.Assert(err, jc.ErrorIsNil) 4036 wpxWatcherC.AssertChange(relx.String()) 4037 wpxWatcherC.AssertNoChange() 4038 } 4039 4040 func removeAllUnits(c *gc.C, s *state.Application) { 4041 us, err := s.AllUnits() 4042 c.Assert(err, jc.ErrorIsNil) 4043 for _, u := range us { 4044 err = u.EnsureDead() 4045 c.Assert(err, jc.ErrorIsNil) 4046 err = u.Remove() 4047 c.Assert(err, jc.ErrorIsNil) 4048 } 4049 } 4050 4051 func (s *ApplicationSuite) TestWatchApplication(c *gc.C) { 4052 w := s.mysql.Watch() 4053 defer testing.AssertStop(c, w) 4054 4055 // Initial event. 4056 wc := testing.NewNotifyWatcherC(c, w) 4057 wc.AssertOneChange() 4058 4059 // Make one change (to a separate instance), check one event. 4060 application, err := s.State.Application(s.mysql.Name()) 4061 c.Assert(err, jc.ErrorIsNil) 4062 err = application.MergeExposeSettings(nil) 4063 c.Assert(err, jc.ErrorIsNil) 4064 wc.AssertOneChange() 4065 4066 // Make two changes, check one event. 4067 err = application.ClearExposed() 4068 c.Assert(err, jc.ErrorIsNil) 4069 // TODO(quiescence): these two changes should be one event. 4070 wc.AssertOneChange() 4071 4072 cfg := state.SetCharmConfig{ 4073 Charm: s.charm, 4074 CharmOrigin: defaultCharmOrigin(s.charm.URL()), 4075 ForceUnits: true, 4076 } 4077 err = application.SetCharm(cfg) 4078 c.Assert(err, jc.ErrorIsNil) 4079 wc.AssertOneChange() 4080 4081 // Stop, check closed. 4082 testing.AssertStop(c, w) 4083 wc.AssertClosed() 4084 4085 // Remove application, start new watch, check single event. 4086 err = application.Destroy() 4087 c.Assert(err, jc.ErrorIsNil) 4088 // The destruction needs to have been processed by the txn watcher before the 4089 // watcher in the test is started or the destroy notification may come through 4090 // as an additional event. 4091 s.WaitForModelWatchersIdle(c, s.Model.UUID()) 4092 w = s.mysql.Watch() 4093 defer testing.AssertStop(c, w) 4094 testing.NewNotifyWatcherC(c, w).AssertOneChange() 4095 } 4096 4097 func (s *ApplicationSuite) TestMetricCredentials(c *gc.C) { 4098 err := s.mysql.SetMetricCredentials([]byte("hello there")) 4099 c.Assert(err, jc.ErrorIsNil) 4100 c.Assert(s.mysql.MetricCredentials(), gc.DeepEquals, []byte("hello there")) 4101 4102 application, err := s.State.Application(s.mysql.Name()) 4103 c.Assert(err, jc.ErrorIsNil) 4104 c.Assert(application.MetricCredentials(), gc.DeepEquals, []byte("hello there")) 4105 } 4106 4107 func (s *ApplicationSuite) TestMetricCredentialsOnDying(c *gc.C) { 4108 _, err := s.mysql.AddUnit(state.AddUnitParams{}) 4109 c.Assert(err, jc.ErrorIsNil) 4110 err = s.mysql.SetMetricCredentials([]byte("set before dying")) 4111 c.Assert(err, jc.ErrorIsNil) 4112 err = s.mysql.Destroy() 4113 c.Assert(err, jc.ErrorIsNil) 4114 assertLife(c, s.mysql, state.Dying) 4115 err = s.mysql.SetMetricCredentials([]byte("set after dying")) 4116 c.Assert(err, gc.ErrorMatches, "cannot update metric credentials: application is not found or not alive") 4117 } 4118 4119 const oneRequiredStorageMeta = ` 4120 storage: 4121 data0: 4122 type: block 4123 ` 4124 4125 const oneOptionalStorageMeta = ` 4126 storage: 4127 data0: 4128 type: block 4129 multiple: 4130 range: 0- 4131 ` 4132 4133 const oneRequiredOneOptionalStorageMeta = ` 4134 storage: 4135 data0: 4136 type: block 4137 data1: 4138 type: block 4139 multiple: 4140 range: 0- 4141 ` 4142 4143 const twoRequiredStorageMeta = ` 4144 storage: 4145 data0: 4146 type: block 4147 data1: 4148 type: block 4149 ` 4150 4151 const twoOptionalStorageMeta = ` 4152 storage: 4153 data0: 4154 type: block 4155 multiple: 4156 range: 0- 4157 data1: 4158 type: block 4159 multiple: 4160 range: 0- 4161 ` 4162 4163 const oneRequiredFilesystemStorageMeta = ` 4164 storage: 4165 data0: 4166 type: filesystem 4167 ` 4168 4169 const oneOptionalSharedStorageMeta = ` 4170 storage: 4171 data0: 4172 type: block 4173 shared: true 4174 multiple: 4175 range: 0- 4176 ` 4177 4178 const oneRequiredReadOnlyStorageMeta = ` 4179 storage: 4180 data0: 4181 type: block 4182 read-only: true 4183 ` 4184 4185 const oneRequiredLocationStorageMeta = ` 4186 storage: 4187 data0: 4188 type: filesystem 4189 location: /srv 4190 ` 4191 4192 const oneMultipleLocationStorageMeta = ` 4193 storage: 4194 data0: 4195 type: filesystem 4196 location: /srv 4197 multiple: 4198 range: 1- 4199 ` 4200 4201 func storageRange(min, max int) string { 4202 var minStr, maxStr string 4203 if min > 0 { 4204 minStr = fmt.Sprint(min) 4205 } 4206 if max > 0 { 4207 maxStr = fmt.Sprint(max) 4208 } 4209 return fmt.Sprintf(` 4210 multiple: 4211 range: %s-%s 4212 `[1:], minStr, maxStr) 4213 } 4214 4215 func (s *ApplicationSuite) setCharmFromMeta(c *gc.C, oldMeta, newMeta string) error { 4216 oldCh := s.AddMetaCharm(c, "mysql", oldMeta, 2) 4217 newCh := s.AddMetaCharm(c, "mysql", newMeta, 3) 4218 app := s.AddTestingApplication(c, "test", oldCh) 4219 4220 cfg := state.SetCharmConfig{ 4221 Charm: newCh, 4222 CharmOrigin: defaultCharmOrigin(newCh.URL()), 4223 } 4224 return app.SetCharm(cfg) 4225 } 4226 4227 func (s *ApplicationSuite) TestSetCharmOptionalUnusedStorageRemoved(c *gc.C) { 4228 err := s.setCharmFromMeta(c, 4229 mysqlBaseMeta+oneRequiredOneOptionalStorageMeta, 4230 mysqlBaseMeta+oneRequiredStorageMeta, 4231 ) 4232 c.Assert(err, jc.ErrorIsNil) 4233 // It's valid to remove optional storage so long 4234 // as it is not in use. 4235 } 4236 4237 func (s *ApplicationSuite) TestSetCharmOptionalUsedStorageRemoved(c *gc.C) { 4238 oldMeta := mysqlBaseMeta + oneRequiredOneOptionalStorageMeta 4239 newMeta := mysqlBaseMeta + oneRequiredStorageMeta 4240 oldCh := s.AddMetaCharm(c, "mysql", oldMeta, 2) 4241 newCh := s.AddMetaCharm(c, "mysql", newMeta, 3) 4242 app := s.Factory.MakeApplication(c, &factory.ApplicationParams{ 4243 Name: "test", 4244 Charm: oldCh, 4245 Storage: map[string]state.StorageConstraints{ 4246 "data0": {Count: 1}, 4247 "data1": {Count: 1}, 4248 }, 4249 }) 4250 defer state.SetBeforeHooks(c, s.State, func() { 4251 // Adding a unit will cause the storage to be in-use. 4252 _, err := app.AddUnit(state.AddUnitParams{}) 4253 c.Assert(err, jc.ErrorIsNil) 4254 }).Check() 4255 cfg := state.SetCharmConfig{ 4256 Charm: newCh, 4257 CharmOrigin: defaultCharmOrigin(newCh.URL()), 4258 } 4259 err := app.SetCharm(cfg) 4260 c.Assert(err, gc.ErrorMatches, `cannot upgrade application "test" to charm "local:quantal/quantal-mysql-3": in-use storage "data1" removed`) 4261 } 4262 4263 func (s *ApplicationSuite) TestSetCharmRequiredStorageRemoved(c *gc.C) { 4264 err := s.setCharmFromMeta(c, 4265 mysqlBaseMeta+oneRequiredStorageMeta, 4266 mysqlBaseMeta, 4267 ) 4268 c.Assert(err, gc.ErrorMatches, `cannot upgrade application "test" to charm "local:quantal/quantal-mysql-3": required storage "data0" removed`) 4269 } 4270 4271 func (s *ApplicationSuite) TestSetCharmRequiredStorageAddedDefaultConstraints(c *gc.C) { 4272 oldCh := s.AddMetaCharm(c, "mysql", mysqlBaseMeta+oneRequiredStorageMeta, 2) 4273 newCh := s.AddMetaCharm(c, "mysql", mysqlBaseMeta+twoRequiredStorageMeta, 3) 4274 app := s.AddTestingApplication(c, "test", oldCh) 4275 u, err := app.AddUnit(state.AddUnitParams{}) 4276 c.Assert(err, jc.ErrorIsNil) 4277 4278 cfg := state.SetCharmConfig{ 4279 Charm: newCh, 4280 CharmOrigin: defaultCharmOrigin(newCh.URL()), 4281 } 4282 err = app.SetCharm(cfg) 4283 c.Assert(err, jc.ErrorIsNil) 4284 4285 // Check that the new required storage was added for the unit. 4286 sb, err := state.NewStorageBackend(s.State) 4287 c.Assert(err, jc.ErrorIsNil) 4288 attachments, err := sb.UnitStorageAttachments(u.UnitTag()) 4289 c.Assert(err, jc.ErrorIsNil) 4290 c.Assert(attachments, gc.HasLen, 2) 4291 } 4292 4293 func (s *ApplicationSuite) TestSetCharmStorageAddedUserSpecifiedConstraints(c *gc.C) { 4294 oldCh := s.AddMetaCharm(c, "mysql", mysqlBaseMeta+oneRequiredStorageMeta, 2) 4295 newCh := s.AddMetaCharm(c, "mysql", mysqlBaseMeta+twoOptionalStorageMeta, 3) 4296 app := s.AddTestingApplication(c, "test", oldCh) 4297 u, err := app.AddUnit(state.AddUnitParams{}) 4298 c.Assert(err, jc.ErrorIsNil) 4299 4300 cfg := state.SetCharmConfig{ 4301 Charm: newCh, 4302 CharmOrigin: defaultCharmOrigin(newCh.URL()), 4303 StorageConstraints: map[string]state.StorageConstraints{ 4304 "data1": {Count: 3}, 4305 }, 4306 } 4307 err = app.SetCharm(cfg) 4308 c.Assert(err, jc.ErrorIsNil) 4309 4310 // Check that new storage was added for the unit, based on the 4311 // constraints specified in SetCharmConfig. 4312 sb, err := state.NewStorageBackend(s.State) 4313 c.Assert(err, jc.ErrorIsNil) 4314 attachments, err := sb.UnitStorageAttachments(u.UnitTag()) 4315 c.Assert(err, jc.ErrorIsNil) 4316 c.Assert(attachments, gc.HasLen, 4) 4317 } 4318 4319 func (s *ApplicationSuite) TestSetCharmOptionalStorageAdded(c *gc.C) { 4320 err := s.setCharmFromMeta(c, 4321 mysqlBaseMeta+oneRequiredStorageMeta, 4322 mysqlBaseMeta+twoOptionalStorageMeta, 4323 ) 4324 c.Assert(err, jc.ErrorIsNil) 4325 } 4326 4327 func (s *ApplicationSuite) TestSetCharmStorageCountMinDecreased(c *gc.C) { 4328 err := s.setCharmFromMeta(c, 4329 mysqlBaseMeta+oneRequiredStorageMeta+storageRange(2, 3), 4330 mysqlBaseMeta+oneRequiredStorageMeta+storageRange(1, 3), 4331 ) 4332 c.Assert(err, jc.ErrorIsNil) 4333 } 4334 4335 func (s *ApplicationSuite) TestSetCharmStorageCountMinIncreased(c *gc.C) { 4336 err := s.setCharmFromMeta(c, 4337 mysqlBaseMeta+oneRequiredStorageMeta+storageRange(1, 3), 4338 mysqlBaseMeta+oneRequiredStorageMeta+storageRange(2, 3), 4339 ) 4340 // User must increase the storage constraints from 1 to 2. 4341 c.Assert(err, gc.ErrorMatches, `cannot upgrade application "test" to charm "local:quantal/quantal-mysql-3": validating storage constraints: charm "mysql" store "data0": 2 instances required, 1 specified`) 4342 } 4343 4344 func (s *ApplicationSuite) TestSetCharmStorageCountMaxDecreased(c *gc.C) { 4345 err := s.setCharmFromMeta(c, 4346 mysqlBaseMeta+oneRequiredStorageMeta+storageRange(1, 2), 4347 mysqlBaseMeta+oneRequiredStorageMeta+storageRange(1, 1), 4348 ) 4349 c.Assert(err, gc.ErrorMatches, `cannot upgrade application "test" to charm "local:quantal/quantal-mysql-3": existing storage "data0" range contracted: max decreased from 2 to 1`) 4350 } 4351 4352 func (s *ApplicationSuite) TestSetCharmStorageCountMaxUnboundedToBounded(c *gc.C) { 4353 err := s.setCharmFromMeta(c, 4354 mysqlBaseMeta+oneRequiredStorageMeta+storageRange(1, -1), 4355 mysqlBaseMeta+oneRequiredStorageMeta+storageRange(1, 999), 4356 ) 4357 c.Assert(err, gc.ErrorMatches, `cannot upgrade application "test" to charm "local:quantal/quantal-mysql-3": existing storage "data0" range contracted: max decreased from \<unbounded\> to 999`) 4358 } 4359 4360 func (s *ApplicationSuite) TestSetCharmStorageTypeChanged(c *gc.C) { 4361 err := s.setCharmFromMeta(c, 4362 mysqlBaseMeta+oneRequiredStorageMeta, 4363 mysqlBaseMeta+oneRequiredFilesystemStorageMeta, 4364 ) 4365 c.Assert(err, gc.ErrorMatches, `cannot upgrade application "test" to charm "local:quantal/quantal-mysql-3": existing storage "data0" type changed from "block" to "filesystem"`) 4366 } 4367 4368 func (s *ApplicationSuite) TestSetCharmStorageSharedChanged(c *gc.C) { 4369 err := s.setCharmFromMeta(c, 4370 mysqlBaseMeta+oneOptionalStorageMeta, 4371 mysqlBaseMeta+oneOptionalSharedStorageMeta, 4372 ) 4373 c.Assert(err, gc.ErrorMatches, `cannot upgrade application "test" to charm "local:quantal/quantal-mysql-3": existing storage "data0" shared changed from false to true`) 4374 } 4375 4376 func (s *ApplicationSuite) TestSetCharmStorageReadOnlyChanged(c *gc.C) { 4377 err := s.setCharmFromMeta(c, 4378 mysqlBaseMeta+oneRequiredStorageMeta, 4379 mysqlBaseMeta+oneRequiredReadOnlyStorageMeta, 4380 ) 4381 c.Assert(err, gc.ErrorMatches, `cannot upgrade application "test" to charm "local:quantal/quantal-mysql-3": existing storage "data0" read-only changed from false to true`) 4382 } 4383 4384 func (s *ApplicationSuite) TestSetCharmStorageLocationChanged(c *gc.C) { 4385 err := s.setCharmFromMeta(c, 4386 mysqlBaseMeta+oneRequiredFilesystemStorageMeta, 4387 mysqlBaseMeta+oneRequiredLocationStorageMeta, 4388 ) 4389 c.Assert(err, gc.ErrorMatches, `cannot upgrade application "test" to charm "local:quantal/quantal-mysql-3": existing storage "data0" location changed from "" to "/srv"`) 4390 } 4391 4392 func (s *ApplicationSuite) TestSetCharmStorageWithLocationSingletonToMultipleAdded(c *gc.C) { 4393 err := s.setCharmFromMeta(c, 4394 mysqlBaseMeta+oneRequiredLocationStorageMeta, 4395 mysqlBaseMeta+oneMultipleLocationStorageMeta, 4396 ) 4397 c.Assert(err, gc.ErrorMatches, `cannot upgrade application "test" to charm "local:quantal/quantal-mysql-3": existing storage "data0" with location changed from single to multiple`) 4398 } 4399 4400 func (s *ApplicationSuite) assertApplicationRemovedWithItsBindings(c *gc.C, application *state.Application) { 4401 // Removing the application removes the bindings with it. 4402 err := application.Destroy() 4403 c.Assert(err, jc.ErrorIsNil) 4404 err = application.Refresh() 4405 c.Assert(err, jc.Satisfies, errors.IsNotFound) 4406 state.AssertEndpointBindingsNotFoundForApplication(c, application) 4407 } 4408 4409 func (s *ApplicationSuite) TestEndpointBindingsReturnsDefaultsWhenNotFound(c *gc.C) { 4410 ch := s.AddMetaCharm(c, "mysql", metaBase, 42) 4411 application := s.AddTestingApplicationWithBindings(c, "yoursql", ch, nil) 4412 state.RemoveEndpointBindingsForApplication(c, application) 4413 4414 s.assertApplicationHasOnlyDefaultEndpointBindings(c, application) 4415 } 4416 4417 func (s *ApplicationSuite) assertApplicationHasOnlyDefaultEndpointBindings(c *gc.C, application *state.Application) { 4418 charm, _, err := application.Charm() 4419 c.Assert(err, jc.ErrorIsNil) 4420 4421 knownEndpoints := set.NewStrings("") 4422 allBindings, err := state.DefaultEndpointBindingsForCharm(s.State, charm.Meta()) 4423 c.Assert(err, jc.ErrorIsNil) 4424 for endpoint := range allBindings { 4425 knownEndpoints.Add(endpoint) 4426 } 4427 4428 setBindings, err := application.EndpointBindings() 4429 c.Assert(err, jc.ErrorIsNil) 4430 c.Assert(setBindings.Map(), gc.NotNil) 4431 4432 for endpoint, space := range setBindings.Map() { 4433 c.Check(knownEndpoints.Contains(endpoint), jc.IsTrue) 4434 c.Check(space, gc.Equals, network.AlphaSpaceId, gc.Commentf("expected default space for endpoint %q, got %q", endpoint, space)) 4435 } 4436 } 4437 4438 func (s *ApplicationSuite) TestEndpointBindingsJustDefaults(c *gc.C) { 4439 // With unspecified bindings, all endpoints are explicitly bound to the 4440 // default space when saved in state. 4441 ch := s.AddMetaCharm(c, "mysql", metaBase, 42) 4442 application := s.AddTestingApplicationWithBindings(c, "yoursql", ch, nil) 4443 4444 s.assertApplicationHasOnlyDefaultEndpointBindings(c, application) 4445 s.assertApplicationRemovedWithItsBindings(c, application) 4446 } 4447 4448 func (s *ApplicationSuite) TestEndpointBindingsWithExplictOverrides(c *gc.C) { 4449 dbSpace, err := s.State.AddSpace("db", "", nil, true) 4450 c.Assert(err, jc.ErrorIsNil) 4451 haSpace, err := s.State.AddSpace("ha", "", nil, false) 4452 c.Assert(err, jc.ErrorIsNil) 4453 4454 bindings := map[string]string{ 4455 "server": dbSpace.Id(), 4456 "cluster": haSpace.Id(), 4457 } 4458 ch := s.AddMetaCharm(c, "mysql", metaBase, 42) 4459 application := s.AddTestingApplicationWithBindings(c, "yoursql", ch, bindings) 4460 4461 setBindings, err := application.EndpointBindings() 4462 c.Assert(err, jc.ErrorIsNil) 4463 c.Assert(setBindings.Map(), jc.DeepEquals, map[string]string{ 4464 "": network.AlphaSpaceId, 4465 "server": dbSpace.Id(), 4466 "client": network.AlphaSpaceId, 4467 "cluster": haSpace.Id(), 4468 }) 4469 4470 s.assertApplicationRemovedWithItsBindings(c, application) 4471 } 4472 4473 func (s *ApplicationSuite) TestSetCharmExtraBindingsUseDefaults(c *gc.C) { 4474 dbSpace, err := s.State.AddSpace("db", "", nil, true) 4475 c.Assert(err, jc.ErrorIsNil) 4476 4477 oldCharm := s.AddMetaCharm(c, "mysql", metaDifferentProvider, 42) 4478 oldBindings := map[string]string{ 4479 "server": dbSpace.Id(), 4480 "kludge": dbSpace.Id(), 4481 "client": dbSpace.Id(), 4482 } 4483 application := s.AddTestingApplicationWithBindings(c, "yoursql", oldCharm, oldBindings) 4484 setBindings, err := application.EndpointBindings() 4485 c.Assert(err, jc.ErrorIsNil) 4486 effectiveOld := map[string]string{ 4487 "": network.AlphaSpaceId, 4488 "server": dbSpace.Id(), 4489 "kludge": dbSpace.Id(), 4490 "client": dbSpace.Id(), 4491 "cluster": network.AlphaSpaceId, 4492 } 4493 c.Assert(setBindings.Map(), jc.DeepEquals, effectiveOld) 4494 4495 newCharm := s.AddMetaCharm(c, "mysql", metaExtraEndpoints, 43) 4496 4497 cfg := state.SetCharmConfig{ 4498 Charm: newCharm, 4499 CharmOrigin: defaultCharmOrigin(newCharm.URL()), 4500 } 4501 err = application.SetCharm(cfg) 4502 c.Assert(err, jc.ErrorIsNil) 4503 setBindings, err = application.EndpointBindings() 4504 c.Assert(err, jc.ErrorIsNil) 4505 effectiveNew := map[string]string{ 4506 "": network.AlphaSpaceId, 4507 // These three should be preserved from oldCharm. 4508 "client": dbSpace.Id(), 4509 "server": dbSpace.Id(), 4510 "cluster": network.AlphaSpaceId, 4511 // "kludge" is missing in newMeta 4512 // All the remaining are new and use the empty default. 4513 "foo": network.AlphaSpaceId, 4514 "baz": network.AlphaSpaceId, 4515 "just": network.AlphaSpaceId, 4516 } 4517 c.Assert(setBindings.Map(), jc.DeepEquals, effectiveNew) 4518 4519 s.assertApplicationRemovedWithItsBindings(c, application) 4520 } 4521 4522 func (s *ApplicationSuite) TestSetCharmHandlesMissingBindingsAsDefaults(c *gc.C) { 4523 oldCharm := s.AddMetaCharm(c, "mysql", metaDifferentProvider, 69) 4524 app := s.AddTestingApplicationWithBindings(c, "theirsql", oldCharm, nil) 4525 state.RemoveEndpointBindingsForApplication(c, app) 4526 4527 newCharm := s.AddMetaCharm(c, "mysql", metaExtraEndpoints, 70) 4528 4529 cfg := state.SetCharmConfig{ 4530 Charm: newCharm, 4531 CharmOrigin: defaultCharmOrigin(newCharm.URL()), 4532 } 4533 err := app.SetCharm(cfg) 4534 c.Assert(err, jc.ErrorIsNil) 4535 setBindings, err := app.EndpointBindings() 4536 c.Assert(err, jc.ErrorIsNil) 4537 effectiveNew := map[string]string{ 4538 // The following two exist for both oldCharm and newCharm. 4539 "client": network.AlphaSpaceId, 4540 "cluster": network.AlphaSpaceId, 4541 // "kludge" is missing in newMeta, "server" is new and gets the default. 4542 "server": network.AlphaSpaceId, 4543 // All the remaining are new and use the empty default. 4544 "foo": network.AlphaSpaceId, 4545 "baz": network.AlphaSpaceId, 4546 "just": network.AlphaSpaceId, 4547 } 4548 c.Assert(setBindings.Map(), jc.DeepEquals, effectiveNew) 4549 4550 s.assertApplicationRemovedWithItsBindings(c, app) 4551 } 4552 4553 func (s *ApplicationSuite) setupApplicationWithUnitsForUpgradeCharmScenario(c *gc.C, numOfUnits int) (deployedV int, err error) { 4554 originalCharmMeta := mysqlBaseMeta + ` 4555 peers: 4556 replication: 4557 interface: pgreplication 4558 ` 4559 originalCharm := s.AddMetaCharm(c, "mysql", originalCharmMeta, 2) 4560 cfg := state.SetCharmConfig{Charm: originalCharm, CharmOrigin: defaultCharmOrigin(originalCharm.URL())} 4561 err = s.mysql.SetCharm(cfg) 4562 c.Assert(err, jc.ErrorIsNil) 4563 s.assertApplicationRelations(c, s.mysql, "mysql:replication") 4564 deployedV = s.mysql.CharmModifiedVersion() 4565 4566 for i := 0; i < numOfUnits; i++ { 4567 _, err = s.mysql.AddUnit(state.AddUnitParams{}) 4568 c.Assert(err, jc.ErrorIsNil) 4569 } 4570 4571 // New mysql charm renames peer relation. 4572 updatedCharmMeta := mysqlBaseMeta + ` 4573 peers: 4574 replication: 4575 interface: pgpeer 4576 ` 4577 updatedCharm := s.AddMetaCharm(c, "mysql", updatedCharmMeta, 3) 4578 4579 cfg = state.SetCharmConfig{Charm: updatedCharm, CharmOrigin: defaultCharmOrigin(updatedCharm.URL())} 4580 err = s.mysql.SetCharm(cfg) 4581 return 4582 } 4583 4584 func (s *ApplicationSuite) TestRenamePeerRelationOnUpgradeWithOneUnit(c *gc.C) { 4585 obtainedV, err := s.setupApplicationWithUnitsForUpgradeCharmScenario(c, 1) 4586 4587 // ensure upgrade happened 4588 c.Assert(err, jc.ErrorIsNil) 4589 c.Assert(s.mysql.CharmModifiedVersion() == obtainedV+1, jc.IsTrue) 4590 } 4591 4592 func (s *ApplicationSuite) TestRenamePeerRelationOnUpgradeWithMoreThanOneUnit(c *gc.C) { 4593 obtainedV, err := s.setupApplicationWithUnitsForUpgradeCharmScenario(c, 2) 4594 4595 // ensure upgrade happened 4596 c.Assert(err, jc.ErrorIsNil) 4597 c.Assert(s.mysql.CharmModifiedVersion() == obtainedV+1, jc.IsTrue) 4598 } 4599 4600 func (s *ApplicationSuite) TestWatchCharmConfig(c *gc.C) { 4601 oldCharm := s.AddTestingCharm(c, "wordpress") 4602 app := s.AddTestingApplication(c, "wordpress", oldCharm) 4603 // Add a unit so when we change the application's charm, 4604 // the old charm isn't removed (due to a reference). 4605 u, err := app.AddUnit(state.AddUnitParams{}) 4606 c.Assert(err, jc.ErrorIsNil) 4607 err = u.SetCharmURL(oldCharm.URL()) 4608 c.Assert(err, jc.ErrorIsNil) 4609 4610 w, err := app.WatchCharmConfig() 4611 c.Assert(err, jc.ErrorIsNil) 4612 defer testing.AssertStop(c, w) 4613 4614 // Initial event. 4615 wc := testing.NewNotifyWatcherC(c, w) 4616 wc.AssertOneChange() 4617 4618 // Update config a couple of times, check a single event. 4619 err = app.UpdateCharmConfig(model.GenerationMaster, charm.Settings{"blog-title": "superhero paparazzi"}) 4620 c.Assert(err, jc.ErrorIsNil) 4621 // TODO(quiescence): these two changes should be one event. 4622 wc.AssertOneChange() 4623 err = app.UpdateCharmConfig(model.GenerationMaster, charm.Settings{"blog-title": "sauceror central"}) 4624 c.Assert(err, jc.ErrorIsNil) 4625 wc.AssertOneChange() 4626 4627 // Non-change is not reported. 4628 err = app.UpdateCharmConfig(model.GenerationMaster, charm.Settings{"blog-title": "sauceror central"}) 4629 c.Assert(err, jc.ErrorIsNil) 4630 wc.AssertNoChange() 4631 4632 // Change application's charm; nothing detected. 4633 newCharm := s.AddConfigCharm(c, "wordpress", stringConfig, 123) 4634 err = app.SetCharm(state.SetCharmConfig{Charm: newCharm, CharmOrigin: defaultCharmOrigin(newCharm.URL())}) 4635 c.Assert(err, jc.ErrorIsNil) 4636 wc.AssertNoChange() 4637 4638 // Change application config for new charm; nothing detected. 4639 err = app.UpdateCharmConfig(model.GenerationMaster, charm.Settings{"key": "value"}) 4640 c.Assert(err, jc.ErrorIsNil) 4641 wc.AssertNoChange() 4642 } 4643 4644 var updateApplicationConfigTests = []struct { 4645 about string 4646 initial config.ConfigAttributes 4647 update config.ConfigAttributes 4648 expect config.ConfigAttributes 4649 err string 4650 }{{ 4651 about: "set string", 4652 update: config.ConfigAttributes{"outlook": "positive"}, 4653 expect: config.ConfigAttributes{"outlook": "positive"}, 4654 }, { 4655 about: "unset string and set another", 4656 initial: config.ConfigAttributes{"outlook": "positive"}, 4657 update: config.ConfigAttributes{"outlook": nil, "title": "sir"}, 4658 expect: config.ConfigAttributes{"title": "sir"}, 4659 }, { 4660 about: "unset missing string", 4661 update: config.ConfigAttributes{"outlook": nil}, 4662 expect: config.ConfigAttributes{}, 4663 }, { 4664 about: `empty strings are valid`, 4665 initial: config.ConfigAttributes{"outlook": "positive"}, 4666 update: config.ConfigAttributes{"outlook": "", "title": ""}, 4667 expect: config.ConfigAttributes{"outlook": "", "title": ""}, 4668 }, { 4669 about: "preserve existing value", 4670 initial: config.ConfigAttributes{"title": "sir"}, 4671 update: config.ConfigAttributes{"username": "admin001"}, 4672 expect: config.ConfigAttributes{"username": "admin001", "title": "sir"}, 4673 }, { 4674 about: "unset a default value, set a different default", 4675 initial: config.ConfigAttributes{"username": "admin001", "title": "sir"}, 4676 update: config.ConfigAttributes{"username": nil, "title": "My Title"}, 4677 expect: config.ConfigAttributes{"title": "My Title"}, 4678 }, { 4679 about: "non-string type", 4680 update: config.ConfigAttributes{"skill-level": 303}, 4681 expect: config.ConfigAttributes{"skill-level": 303}, 4682 }, { 4683 about: "unset non-string type", 4684 initial: config.ConfigAttributes{"skill-level": 303}, 4685 update: config.ConfigAttributes{"skill-level": nil}, 4686 expect: config.ConfigAttributes{}, 4687 }} 4688 4689 func (s *ApplicationSuite) TestUpdateApplicationConfig(c *gc.C) { 4690 sch := s.AddTestingCharm(c, "dummy") 4691 for i, t := range updateApplicationConfigTests { 4692 c.Logf("test %d. %s", i, t.about) 4693 app := s.AddTestingApplication(c, "dummy-application", sch) 4694 if t.initial != nil { 4695 err := app.UpdateApplicationConfig(t.initial, nil, sampleApplicationConfigSchema(), nil) 4696 c.Assert(err, jc.ErrorIsNil) 4697 } 4698 updates := make(map[string]interface{}) 4699 var resets []string 4700 for k, v := range t.update { 4701 if v == nil { 4702 resets = append(resets, k) 4703 } else { 4704 updates[k] = v 4705 } 4706 } 4707 err := app.UpdateApplicationConfig(updates, resets, sampleApplicationConfigSchema(), nil) 4708 if t.err != "" { 4709 c.Assert(err, gc.ErrorMatches, t.err) 4710 } else { 4711 c.Assert(err, jc.ErrorIsNil) 4712 cfg, err := app.ApplicationConfig() 4713 c.Assert(err, jc.ErrorIsNil) 4714 c.Assert(cfg, gc.DeepEquals, t.expect) 4715 } 4716 err = app.Destroy() 4717 c.Assert(err, jc.ErrorIsNil) 4718 } 4719 } 4720 4721 func (s *ApplicationSuite) TestApplicationConfigNotFoundNoError(c *gc.C) { 4722 ch := s.AddTestingCharm(c, "dummy") 4723 app := s.AddTestingApplication(c, "dummy-application", ch) 4724 4725 // Delete all the settings. We should get a nil return, but no error. 4726 _, _ = s.State.MongoSession().DB("juju").C("settings").RemoveAll(nil) 4727 4728 cfg, err := app.ApplicationConfig() 4729 c.Assert(err, jc.ErrorIsNil) 4730 c.Assert(cfg, gc.HasLen, 0) 4731 } 4732 4733 func (s *ApplicationSuite) TestStatusInitial(c *gc.C) { 4734 appStatus, err := s.mysql.Status() 4735 c.Check(err, jc.ErrorIsNil) 4736 c.Check(appStatus.Status, gc.Equals, status.Unset) 4737 c.Check(appStatus.Message, gc.Equals, "") 4738 c.Check(appStatus.Data, gc.HasLen, 0) 4739 } 4740 4741 func (s *ApplicationSuite) TestUnitStatusesNoUnits(c *gc.C) { 4742 statuses, err := s.mysql.UnitStatuses() 4743 c.Check(err, jc.ErrorIsNil) 4744 c.Check(statuses, gc.HasLen, 0) 4745 } 4746 4747 func (s *ApplicationSuite) TestUnitStatusesWithUnits(c *gc.C) { 4748 u1, err := s.mysql.AddUnit(state.AddUnitParams{}) 4749 c.Assert(err, jc.ErrorIsNil) 4750 err = u1.SetStatus(status.StatusInfo{ 4751 Status: status.Maintenance, 4752 }) 4753 c.Assert(err, jc.ErrorIsNil) 4754 4755 // If Agent status is in error, we see that. 4756 u2, err := s.mysql.AddUnit(state.AddUnitParams{}) 4757 c.Assert(err, jc.ErrorIsNil) 4758 err = u2.Agent().SetStatus(status.StatusInfo{ 4759 Status: status.Error, 4760 Message: "foo", 4761 }) 4762 c.Assert(err, jc.ErrorIsNil) 4763 err = u2.SetStatus(status.StatusInfo{ 4764 Status: status.Blocked, 4765 }) 4766 c.Assert(err, jc.ErrorIsNil) 4767 4768 statuses, err := s.mysql.UnitStatuses() 4769 c.Check(err, jc.ErrorIsNil) 4770 4771 check := jc.NewMultiChecker() 4772 check.AddExpr(`_[_].Since`, jc.Ignore) 4773 check.AddExpr(`_[_].Data`, jc.Ignore) 4774 c.Assert(statuses, check, map[string]status.StatusInfo{ 4775 "mysql/0": { 4776 Status: status.Maintenance, 4777 }, 4778 "mysql/1": { 4779 Status: status.Error, 4780 Message: "foo", 4781 }, 4782 }) 4783 } 4784 4785 func sampleApplicationConfigSchema() environschema.Fields { 4786 schema := environschema.Fields{ 4787 "title": environschema.Attr{Type: environschema.Tstring}, 4788 "outlook": environschema.Attr{Type: environschema.Tstring}, 4789 "username": environschema.Attr{Type: environschema.Tstring}, 4790 "skill-level": environschema.Attr{Type: environschema.Tint}, 4791 } 4792 return schema 4793 } 4794 4795 func (s *ApplicationSuite) TestUpdateApplicationConfigWithDyingApplication(c *gc.C) { 4796 _, err := s.mysql.AddUnit(state.AddUnitParams{}) 4797 c.Assert(err, jc.ErrorIsNil) 4798 err = s.mysql.Destroy() 4799 c.Assert(err, jc.ErrorIsNil) 4800 assertLife(c, s.mysql, state.Dying) 4801 err = s.mysql.UpdateApplicationConfig(config.ConfigAttributes{"title": "value"}, nil, sampleApplicationConfigSchema(), nil) 4802 c.Assert(err, jc.ErrorIsNil) 4803 } 4804 4805 func (s *ApplicationSuite) TestDestroyApplicationRemovesConfig(c *gc.C) { 4806 err := s.mysql.UpdateApplicationConfig(config.ConfigAttributes{"title": "value"}, nil, sampleApplicationConfigSchema(), nil) 4807 c.Assert(err, jc.ErrorIsNil) 4808 appConfig := state.GetApplicationConfig(s.State, s.mysql) 4809 err = appConfig.Read() 4810 c.Assert(err, jc.ErrorIsNil) 4811 c.Assert(appConfig.Map(), gc.Not(gc.HasLen), 0) 4812 4813 op := s.mysql.DestroyOperation() 4814 op.RemoveOffers = true 4815 err = s.State.ApplyOperation(op) 4816 c.Assert(err, jc.ErrorIsNil) 4817 assertRemoved(c, s.mysql) 4818 } 4819 4820 type CAASApplicationSuite struct { 4821 ConnSuite 4822 app *state.Application 4823 caasSt *state.State 4824 } 4825 4826 var _ = gc.Suite(&CAASApplicationSuite{}) 4827 4828 func (s *CAASApplicationSuite) SetUpTest(c *gc.C) { 4829 s.ConnSuite.SetUpTest(c) 4830 s.caasSt = s.Factory.MakeCAASModel(c, nil) 4831 s.AddCleanup(func(_ *gc.C) { _ = s.caasSt.Close() }) 4832 4833 f := factory.NewFactory(s.caasSt, s.StatePool) 4834 ch := f.MakeCharm(c, &factory.CharmParams{Name: "gitlab", Series: "kubernetes"}) 4835 s.app = f.MakeApplication(c, &factory.ApplicationParams{Name: "gitlab", Charm: ch}) 4836 // Consume the initial construction events from the watchers. 4837 s.WaitForModelWatchersIdle(c, s.Model.UUID()) 4838 } 4839 4840 func strPtr(s string) *string { 4841 return &s 4842 } 4843 4844 func (s *CAASApplicationSuite) TestUpdateCAASUnits(c *gc.C) { 4845 s.assertUpdateCAASUnits(c, true) 4846 } 4847 4848 func (s *CAASApplicationSuite) TestUpdateCAASUnitsApplicationNotALive(c *gc.C) { 4849 s.assertUpdateCAASUnits(c, false) 4850 } 4851 4852 func (s *CAASApplicationSuite) assertUpdateCAASUnits(c *gc.C, aliveApp bool) { 4853 existingUnit, err := s.app.AddUnit(state.AddUnitParams{ProviderId: strPtr("unit-uuid")}) 4854 c.Assert(err, jc.ErrorIsNil) 4855 removedUnit, err := s.app.AddUnit(state.AddUnitParams{ProviderId: strPtr("removed-unit-uuid")}) 4856 c.Assert(err, jc.ErrorIsNil) 4857 noContainerUnit, err := s.app.AddUnit(state.AddUnitParams{ProviderId: strPtr("never-cloud-container")}) 4858 c.Assert(err, jc.ErrorIsNil) 4859 if !aliveApp { 4860 err := s.app.Destroy() 4861 c.Assert(err, jc.ErrorIsNil) 4862 } 4863 4864 var updateUnits state.UpdateUnitsOperation 4865 updateUnits.Deletes = []*state.DestroyUnitOperation{removedUnit.DestroyOperation()} 4866 updateUnits.Adds = []*state.AddUnitOperation{ 4867 s.app.AddOperation(state.UnitUpdateProperties{ 4868 ProviderId: strPtr("new-unit-uuid"), 4869 Address: strPtr("192.168.1.1"), 4870 Ports: &[]string{"80"}, 4871 AgentStatus: &status.StatusInfo{ 4872 Status: status.Running, 4873 Message: "new running", 4874 }, 4875 CloudContainerStatus: &status.StatusInfo{ 4876 Status: status.Running, 4877 Message: "new container running", 4878 }, 4879 }), 4880 s.app.AddOperation(state.UnitUpdateProperties{ 4881 ProviderId: strPtr("add-never-cloud-container"), 4882 AgentStatus: &status.StatusInfo{ 4883 Status: status.Running, 4884 Message: "new running", 4885 }, 4886 // Status history should not show this as active. 4887 UnitStatus: &status.StatusInfo{ 4888 Status: status.Active, 4889 Message: "unit active", 4890 }, 4891 }), 4892 } 4893 updateUnits.Updates = []*state.UpdateUnitOperation{ 4894 noContainerUnit.UpdateOperation(state.UnitUpdateProperties{ 4895 ProviderId: strPtr("never-cloud-container"), 4896 Address: strPtr("192.168.1.2"), 4897 Ports: &[]string{"443"}, 4898 UnitStatus: &status.StatusInfo{ 4899 Status: status.Active, 4900 Message: "unit active", 4901 }, 4902 }), 4903 existingUnit.UpdateOperation(state.UnitUpdateProperties{ 4904 ProviderId: strPtr("unit-uuid"), 4905 Address: strPtr("192.168.1.2"), 4906 Ports: &[]string{"443"}, 4907 AgentStatus: &status.StatusInfo{ 4908 Status: status.Running, 4909 Message: "existing running", 4910 }, 4911 CloudContainerStatus: &status.StatusInfo{ 4912 Status: status.Running, 4913 Message: "existing container running", 4914 }, 4915 })} 4916 err = s.app.UpdateUnits(&updateUnits) 4917 if !aliveApp { 4918 c.Assert(err, jc.Satisfies, state.IsNotAlive) 4919 return 4920 } 4921 c.Assert(err, jc.ErrorIsNil) 4922 4923 units, err := s.app.AllUnits() 4924 c.Assert(err, jc.ErrorIsNil) 4925 c.Assert(units, gc.HasLen, 4) 4926 4927 unitsById := make(map[string]*state.Unit) 4928 containerInfoById := make(map[string]state.CloudContainer) 4929 for _, u := range units { 4930 c.Assert(u.ShouldBeAssigned(), jc.IsFalse) 4931 containerInfo, err := u.ContainerInfo() 4932 c.Assert(err, jc.ErrorIsNil) 4933 c.Assert(containerInfo.Unit(), gc.Equals, u.Name()) 4934 c.Assert(containerInfo.ProviderId(), gc.Not(gc.Equals), "") 4935 unitsById[containerInfo.ProviderId()] = u 4936 containerInfoById[containerInfo.ProviderId()] = containerInfo 4937 } 4938 u, ok := unitsById["unit-uuid"] 4939 c.Assert(ok, jc.IsTrue) 4940 info, ok := containerInfoById["unit-uuid"] 4941 c.Assert(ok, jc.IsTrue) 4942 c.Check(u.Name(), gc.Equals, existingUnit.Name()) 4943 c.Check(info.Address(), gc.NotNil) 4944 c.Check(*info.Address(), gc.DeepEquals, 4945 network.NewSpaceAddress("192.168.1.2", network.WithScope(network.ScopeMachineLocal))) 4946 c.Check(info.Ports(), jc.DeepEquals, []string{"443"}) 4947 statusInfo, err := u.AgentStatus() 4948 c.Assert(err, jc.ErrorIsNil) 4949 c.Assert(statusInfo.Status, gc.Equals, status.Running) 4950 c.Assert(statusInfo.Message, gc.Equals, "existing running") 4951 history, err := u.AgentHistory().StatusHistory(status.StatusHistoryFilter{Size: 10}) 4952 c.Assert(err, jc.ErrorIsNil) 4953 c.Assert(history, gc.HasLen, 2) 4954 // Creating a new unit may cause the history entries to be written with 4955 // the same timestamp due to the precision used by the db. 4956 if history[0].Status == status.Running { 4957 c.Assert(history[0].Status, gc.Equals, status.Running) 4958 c.Assert(history[1].Status, gc.Equals, status.Allocating) 4959 } else { 4960 c.Assert(history[1].Status, gc.Equals, status.Running) 4961 c.Assert(history[0].Status, gc.Equals, status.Allocating) 4962 c.Assert(history[0].Since.Unix(), gc.Equals, history[1].Since.Unix()) 4963 } 4964 statusInfo, err = u.Status() 4965 c.Assert(err, jc.ErrorIsNil) 4966 c.Assert(statusInfo.Status, gc.Equals, status.Waiting) 4967 c.Assert(statusInfo.Message, gc.Equals, "installing agent") 4968 statusInfo, err = state.GetCloudContainerStatus(s.caasSt, u.Name()) 4969 c.Assert(err, jc.ErrorIsNil) 4970 c.Assert(statusInfo.Status, gc.Equals, status.Running) 4971 c.Assert(statusInfo.Message, gc.Equals, "existing container running") 4972 unitHistory, err := u.StatusHistory(status.StatusHistoryFilter{Size: 10}) 4973 c.Assert(err, jc.ErrorIsNil) 4974 c.Assert(unitHistory, gc.HasLen, 2) 4975 // Creating a new unit may cause the history entries to be written with 4976 // the same timestamp due to the precision used by the db. 4977 if unitHistory[0].Status == status.Running { 4978 c.Assert(unitHistory[0].Status, gc.Equals, status.Running) 4979 c.Assert(unitHistory[0].Message, gc.Equals, "existing container running") 4980 c.Assert(unitHistory[1].Status, gc.Equals, status.Waiting) 4981 } else { 4982 c.Assert(unitHistory[1].Status, gc.Equals, status.Running) 4983 c.Assert(unitHistory[1].Message, gc.Equals, "existing container running") 4984 c.Assert(unitHistory[0].Status, gc.Equals, status.Waiting) 4985 c.Assert(unitHistory[0].Since.Unix(), gc.Equals, history[1].Since.Unix()) 4986 } 4987 4988 u, ok = unitsById["never-cloud-container"] 4989 c.Assert(ok, jc.IsTrue) 4990 info, ok = containerInfoById["never-cloud-container"] 4991 c.Assert(ok, jc.IsTrue) 4992 unitHistory, err = u.StatusHistory(status.StatusHistoryFilter{Size: 10}) 4993 c.Assert(err, jc.ErrorIsNil) 4994 c.Assert(unitHistory[0].Status, gc.Equals, status.Waiting) 4995 c.Assert(unitHistory[0].Message, gc.Equals, status.MessageInstallingAgent) 4996 4997 u, ok = unitsById["add-never-cloud-container"] 4998 c.Assert(ok, jc.IsTrue) 4999 info, ok = containerInfoById["add-never-cloud-container"] 5000 c.Assert(ok, jc.IsTrue) 5001 unitHistory, err = u.StatusHistory(status.StatusHistoryFilter{Size: 10}) 5002 c.Assert(err, jc.ErrorIsNil) 5003 c.Assert(unitHistory[0].Status, gc.Equals, status.Waiting) 5004 c.Assert(unitHistory[0].Message, gc.Equals, status.MessageInstallingAgent) 5005 5006 u, ok = unitsById["new-unit-uuid"] 5007 c.Assert(ok, jc.IsTrue) 5008 info, ok = containerInfoById["new-unit-uuid"] 5009 c.Assert(ok, jc.IsTrue) 5010 c.Assert(u.Name(), gc.Equals, "gitlab/3") 5011 c.Check(info.Address(), gc.NotNil) 5012 c.Check(*info.Address(), gc.DeepEquals, 5013 network.NewSpaceAddress("192.168.1.1", network.WithScope(network.ScopeMachineLocal))) 5014 c.Assert(info.Ports(), jc.DeepEquals, []string{"80"}) 5015 5016 addr, err := u.PrivateAddress() 5017 c.Assert(err, jc.ErrorIsNil) 5018 c.Assert(addr, jc.DeepEquals, network.NewSpaceAddress("192.168.1.1", network.WithScope(network.ScopeMachineLocal))) 5019 5020 statusInfo, err = u.Status() 5021 c.Assert(err, jc.ErrorIsNil) 5022 c.Assert(statusInfo.Status, gc.Equals, status.Waiting) 5023 c.Assert(statusInfo.Message, gc.Equals, status.MessageInstallingAgent) 5024 statusInfo, err = state.GetCloudContainerStatus(s.caasSt, u.Name()) 5025 c.Assert(err, jc.ErrorIsNil) 5026 c.Assert(statusInfo.Status, gc.Equals, status.Running) 5027 c.Assert(statusInfo.Message, gc.Equals, "new container running") 5028 statusInfo, err = u.AgentStatus() 5029 c.Assert(err, jc.ErrorIsNil) 5030 c.Assert(statusInfo.Status, gc.Equals, status.Running) 5031 c.Assert(statusInfo.Message, gc.Equals, "new running") 5032 history, err = u.AgentHistory().StatusHistory(status.StatusHistoryFilter{Size: 10}) 5033 c.Assert(err, jc.ErrorIsNil) 5034 c.Assert(history, gc.HasLen, 2) 5035 // Creating a new unit may cause the history entries to be written with 5036 // the same timestamp due to the precision used by the db. 5037 if history[0].Status == status.Running { 5038 c.Assert(history[0].Status, gc.Equals, status.Running) 5039 c.Assert(history[1].Status, gc.Equals, status.Allocating) 5040 } else { 5041 c.Assert(history[1].Status, gc.Equals, status.Running) 5042 c.Assert(history[0].Status, gc.Equals, status.Allocating) 5043 c.Assert(history[0].Since.Unix(), gc.Equals, history[1].Since.Unix()) 5044 } 5045 // container status history must have overridden the unit status. 5046 unitHistory, err = u.StatusHistory(status.StatusHistoryFilter{Size: 10}) 5047 c.Assert(err, jc.ErrorIsNil) 5048 c.Assert(unitHistory, gc.HasLen, 2) 5049 // Creating a new unit may cause the history entries to be written with 5050 // the same timestamp due to the precision used by the db. 5051 if unitHistory[0].Status == status.Running { 5052 c.Assert(unitHistory[0].Status, gc.Equals, status.Running) 5053 c.Assert(unitHistory[0].Message, gc.Equals, "new container running") 5054 c.Assert(unitHistory[1].Status, gc.Equals, status.Waiting) 5055 } else { 5056 c.Assert(unitHistory[1].Status, gc.Equals, status.Running) 5057 c.Assert(unitHistory[1].Message, gc.Equals, "new container running") 5058 c.Assert(unitHistory[0].Status, gc.Equals, status.Waiting) 5059 c.Assert(unitHistory[0].Since.Unix(), gc.Equals, history[1].Since.Unix()) 5060 } 5061 5062 // check cloud container status history is stored. 5063 containerStatusHistory, err := state.GetCloudContainerStatusHistory(s.caasSt, u.Name(), status.StatusHistoryFilter{Size: 10}) 5064 c.Assert(err, jc.ErrorIsNil) 5065 c.Assert(containerStatusHistory, gc.HasLen, 1) 5066 c.Assert(containerStatusHistory[0].Status, gc.Equals, status.Running) 5067 c.Assert(containerStatusHistory[0].Message, gc.Equals, "new container running") 5068 5069 err = removedUnit.Refresh() 5070 c.Assert(err, jc.Satisfies, errors.IsNotFound) 5071 } 5072 5073 func (s *CAASApplicationSuite) TestAddUnitWithProviderId(c *gc.C) { 5074 u, err := s.app.AddUnit(state.AddUnitParams{ProviderId: strPtr("provider-id")}) 5075 c.Assert(err, jc.ErrorIsNil) 5076 info, err := u.ContainerInfo() 5077 c.Assert(err, jc.ErrorIsNil) 5078 c.Assert(info.Unit(), gc.Equals, u.Name()) 5079 c.Assert(info.ProviderId(), gc.Equals, "provider-id") 5080 } 5081 5082 func (s *CAASApplicationSuite) TestServiceInfo(c *gc.C) { 5083 addrs := network.NewSpaceAddresses("10.0.0.1") 5084 5085 for i := 0; i < 2; i++ { 5086 err := s.app.UpdateCloudService("id", addrs) 5087 c.Assert(err, jc.ErrorIsNil) 5088 app, err := s.caasSt.Application(s.app.Name()) 5089 c.Assert(err, jc.ErrorIsNil) 5090 info, err := app.ServiceInfo() 5091 c.Assert(err, jc.ErrorIsNil) 5092 c.Assert(info.ProviderId(), gc.Equals, "id") 5093 c.Assert(info.Addresses(), jc.DeepEquals, addrs) 5094 } 5095 } 5096 5097 func (s *CAASApplicationSuite) TestServiceInfoEmptyProviderId(c *gc.C) { 5098 addrs := network.NewSpaceAddresses("10.0.0.1") 5099 5100 for i := 0; i < 2; i++ { 5101 err := s.app.UpdateCloudService("", addrs) 5102 c.Assert(err, jc.ErrorIsNil) 5103 app, err := s.caasSt.Application(s.app.Name()) 5104 c.Assert(err, jc.ErrorIsNil) 5105 info, err := app.ServiceInfo() 5106 c.Assert(err, jc.ErrorIsNil) 5107 c.Assert(info.ProviderId(), gc.Equals, "") 5108 c.Assert(info.Addresses(), jc.DeepEquals, addrs) 5109 } 5110 } 5111 5112 func (s *CAASApplicationSuite) TestRemoveApplicationDeletesServiceInfo(c *gc.C) { 5113 addrs := network.NewSpaceAddresses("10.0.0.1") 5114 5115 err := s.app.UpdateCloudService("id", addrs) 5116 c.Assert(err, jc.ErrorIsNil) 5117 err = s.app.Destroy() 5118 c.Assert(err, jc.ErrorIsNil) 5119 err = s.app.ClearResources() 5120 c.Assert(err, jc.ErrorIsNil) 5121 // Until cleanups run, no removal. 5122 si, err := s.app.ServiceInfo() 5123 c.Assert(err, jc.ErrorIsNil) 5124 c.Assert(si, gc.NotNil) 5125 assertCleanupCount(c, s.caasSt, 2) 5126 _, err = s.app.ServiceInfo() 5127 c.Assert(err, jc.Satisfies, errors.IsNotFound) 5128 } 5129 5130 func (s *CAASApplicationSuite) TestInvalidScale(c *gc.C) { 5131 err := s.app.SetScale(-1, 0, true) 5132 c.Assert(err, gc.ErrorMatches, "application scale -1 not valid") 5133 5134 // set scale without force for caas workers - a new Generation is required. 5135 err = s.app.SetScale(3, 0, false) 5136 c.Assert(err, jc.Satisfies, errors.IsForbidden) 5137 } 5138 5139 func (s *CAASApplicationSuite) TestSetScale(c *gc.C) { 5140 // set scale with force for CLI - DesiredScaleProtected set to true. 5141 err := s.app.SetScale(5, 0, true) 5142 c.Assert(err, jc.ErrorIsNil) 5143 err = s.app.Refresh() 5144 c.Assert(err, jc.ErrorIsNil) 5145 c.Assert(s.app.GetScale(), gc.Equals, 5) 5146 svcInfo, err := s.app.ServiceInfo() 5147 c.Assert(err, jc.ErrorIsNil) 5148 c.Assert(svcInfo.DesiredScaleProtected(), jc.IsTrue) 5149 5150 // set scale without force for caas workers - a new Generation is required. 5151 err = s.app.SetScale(5, 1, false) 5152 c.Assert(err, jc.ErrorIsNil) 5153 err = s.app.Refresh() 5154 c.Assert(err, jc.ErrorIsNil) 5155 c.Assert(s.app.GetScale(), gc.Equals, 5) 5156 svcInfo, err = s.app.ServiceInfo() 5157 c.Assert(err, jc.ErrorIsNil) 5158 c.Assert(svcInfo.DesiredScaleProtected(), jc.IsFalse) 5159 c.Assert(svcInfo.Generation(), jc.DeepEquals, int64(1)) 5160 } 5161 5162 func (s *CAASApplicationSuite) TestInvalidChangeScale(c *gc.C) { 5163 newScale, err := s.app.ChangeScale(-1) 5164 c.Assert(err, gc.ErrorMatches, "cannot remove more units than currently exist not valid") 5165 c.Assert(newScale, gc.Equals, 0) 5166 } 5167 5168 func (s *CAASApplicationSuite) TestChangeScale(c *gc.C) { 5169 newScale, err := s.app.ChangeScale(5) 5170 c.Assert(err, jc.ErrorIsNil) 5171 c.Assert(newScale, gc.Equals, 5) 5172 err = s.app.Refresh() 5173 c.Assert(err, jc.ErrorIsNil) 5174 c.Assert(s.app.GetScale(), gc.Equals, 5) 5175 5176 newScale, err = s.app.ChangeScale(-4) 5177 c.Assert(err, jc.ErrorIsNil) 5178 c.Assert(newScale, gc.Equals, 1) 5179 err = s.app.Refresh() 5180 c.Assert(err, jc.ErrorIsNil) 5181 c.Assert(s.app.GetScale(), gc.Equals, 1) 5182 } 5183 5184 func (s *CAASApplicationSuite) TestWatchScale(c *gc.C) { 5185 // Empty initial event. 5186 w := s.app.WatchScale() 5187 defer testing.AssertStop(c, w) 5188 wc := testing.NewNotifyWatcherC(c, w) 5189 wc.AssertOneChange() 5190 5191 err := s.app.SetScale(5, 0, true) 5192 c.Assert(err, jc.ErrorIsNil) 5193 wc.AssertOneChange() 5194 5195 // Set to same value, no change. 5196 err = s.app.SetScale(5, 0, true) 5197 c.Assert(err, jc.ErrorIsNil) 5198 wc.AssertNoChange() 5199 5200 err = s.app.SetScale(6, 0, true) 5201 c.Assert(err, jc.ErrorIsNil) 5202 wc.AssertOneChange() 5203 5204 // An unrelated update, no change. 5205 err = s.app.SetMinUnits(2) 5206 c.Assert(err, jc.ErrorIsNil) 5207 wc.AssertNoChange() 5208 5209 err = s.app.Destroy() 5210 c.Assert(err, jc.ErrorIsNil) 5211 wc.AssertNoChange() 5212 } 5213 5214 func (s *CAASApplicationSuite) TestWatchCloudService(c *gc.C) { 5215 cloudSvc, err := s.State.SaveCloudService(state.SaveCloudServiceArgs{ 5216 Id: s.app.Name(), 5217 }) 5218 c.Assert(err, jc.ErrorIsNil) 5219 s.WaitForModelWatchersIdle(c, s.Model.UUID()) 5220 5221 w := cloudSvc.Watch() 5222 defer testing.AssertStop(c, w) 5223 5224 // Initial event. 5225 wc := testing.NewNotifyWatcherC(c, w) 5226 wc.AssertOneChange() 5227 5228 _, err = s.State.SaveCloudService(state.SaveCloudServiceArgs{ 5229 Id: s.app.Name(), 5230 ProviderId: "123", 5231 }) 5232 c.Assert(err, jc.ErrorIsNil) 5233 wc.AssertOneChange() 5234 5235 // Stop, check closed. 5236 testing.AssertStop(c, w) 5237 wc.AssertClosed() 5238 5239 // Remove service by removing app, start new watch, check single event. 5240 err = s.app.Destroy() 5241 c.Assert(err, jc.ErrorIsNil) 5242 s.WaitForModelWatchersIdle(c, s.Model.UUID()) 5243 w = cloudSvc.Watch() 5244 defer testing.AssertStop(c, w) 5245 testing.NewNotifyWatcherC(c, w).AssertOneChange() 5246 } 5247 5248 func (s *CAASApplicationSuite) TestRewriteStatusHistory(c *gc.C) { 5249 st := s.Factory.MakeModel(c, &factory.ModelParams{ 5250 Name: "caas-model", 5251 Type: state.ModelTypeCAAS, 5252 }) 5253 defer st.Close() 5254 f := factory.NewFactory(st, s.StatePool) 5255 ch := f.MakeCharm(c, &factory.CharmParams{Name: "gitlab", Series: "kubernetes"}) 5256 app := f.MakeApplication(c, &factory.ApplicationParams{Name: "gitlab", Charm: ch}) 5257 5258 history, err := app.StatusHistory(status.StatusHistoryFilter{Size: 10}) 5259 c.Assert(err, jc.ErrorIsNil) 5260 c.Assert(history, gc.HasLen, 1) 5261 c.Assert(history[0].Status, gc.Equals, status.Unset) 5262 c.Assert(history[0].Message, gc.Equals, "") 5263 5264 // Must overwrite the history 5265 err = app.SetOperatorStatus(status.StatusInfo{ 5266 Status: status.Allocating, 5267 Message: "operator message", 5268 }) 5269 c.Assert(err, jc.ErrorIsNil) 5270 history, err = app.StatusHistory(status.StatusHistoryFilter{Size: 10}) 5271 c.Assert(err, jc.ErrorIsNil) 5272 c.Assert(history, gc.HasLen, 2) 5273 c.Assert(history[0].Status, gc.Equals, status.Allocating) 5274 c.Assert(history[0].Message, gc.Equals, "operator message") 5275 c.Assert(history[1].Status, gc.Equals, status.Unset) 5276 c.Assert(history[1].Message, gc.Equals, "") 5277 5278 err = app.SetOperatorStatus(status.StatusInfo{ 5279 Status: status.Running, 5280 Message: "operator running", 5281 }) 5282 c.Assert(err, jc.ErrorIsNil) 5283 err = app.SetStatus(status.StatusInfo{ 5284 Status: status.Active, 5285 Message: "app active", 5286 }) 5287 c.Assert(err, jc.ErrorIsNil) 5288 history, err = app.StatusHistory(status.StatusHistoryFilter{Size: 10}) 5289 c.Log(history) 5290 c.Assert(err, jc.ErrorIsNil) 5291 c.Assert(history, gc.HasLen, 3) 5292 c.Assert(history[0].Status, gc.Equals, status.Active) 5293 c.Assert(history[0].Message, gc.Equals, "app active") 5294 c.Assert(history[1].Status, gc.Equals, status.Allocating) 5295 c.Assert(history[1].Message, gc.Equals, "operator message") 5296 c.Assert(history[2].Status, gc.Equals, status.Unset) 5297 c.Assert(history[2].Message, gc.Equals, "") 5298 } 5299 5300 func (s *CAASApplicationSuite) TestClearResources(c *gc.C) { 5301 c.Assert(state.GetApplicationHasResources(s.app), jc.IsTrue) 5302 err := s.app.ClearResources() 5303 c.Assert(err, gc.ErrorMatches, `application "gitlab" is alive`) 5304 err = s.app.Destroy() 5305 c.Assert(err, jc.ErrorIsNil) 5306 assertCleanupCount(c, s.caasSt, 1) 5307 5308 // ClearResources should be idempotent. 5309 for i := 0; i < 2; i++ { 5310 err := s.app.ClearResources() 5311 c.Assert(err, jc.ErrorIsNil) 5312 c.Assert(state.GetApplicationHasResources(s.app), jc.IsFalse) 5313 } 5314 // Resetting the app's HasResources the first time schedules a cleanup. 5315 assertCleanupCount(c, s.caasSt, 2) 5316 } 5317 5318 func (s *CAASApplicationSuite) TestDestroySimple(c *gc.C) { 5319 err := s.app.Destroy() 5320 c.Assert(err, jc.ErrorIsNil) 5321 // App not removed since cluster resources not cleaned up yet. 5322 c.Assert(s.app.Life(), gc.Equals, state.Dead) 5323 err = s.app.Refresh() 5324 c.Assert(err, jc.ErrorIsNil) 5325 c.Assert(state.GetApplicationHasResources(s.app), jc.IsTrue) 5326 } 5327 5328 func (s *CAASApplicationSuite) TestForceDestroyQueuesForceCleanup(c *gc.C) { 5329 op := s.app.DestroyOperation() 5330 op.Force = true 5331 err := s.caasSt.ApplyOperation(op) 5332 c.Assert(err, jc.ErrorIsNil) 5333 5334 // Cleanup queued but won't run until scheduled. 5335 assertNeedsCleanup(c, s.caasSt) 5336 s.Clock.Advance(2 * time.Minute) 5337 assertCleanupRuns(c, s.caasSt) 5338 5339 err = s.app.Refresh() 5340 c.Assert(err, jc.Satisfies, errors.IsNotFound) 5341 } 5342 5343 func (s *CAASApplicationSuite) TestDestroyStillHasUnits(c *gc.C) { 5344 unit, err := s.app.AddUnit(state.AddUnitParams{}) 5345 c.Assert(err, jc.ErrorIsNil) 5346 err = s.app.Destroy() 5347 c.Assert(err, jc.ErrorIsNil) 5348 c.Assert(s.app.Life(), gc.Equals, state.Dying) 5349 5350 c.Assert(unit.EnsureDead(), jc.ErrorIsNil) 5351 assertLife(c, s.app, state.Dying) 5352 5353 c.Assert(unit.Remove(), jc.ErrorIsNil) 5354 assertCleanupCount(c, s.caasSt, 1) 5355 // App not removed since cluster resources not cleaned up yet. 5356 assertLife(c, s.app, state.Dead) 5357 } 5358 5359 func (s *CAASApplicationSuite) TestDestroyOnceHadUnits(c *gc.C) { 5360 unit, err := s.app.AddUnit(state.AddUnitParams{}) 5361 c.Assert(err, jc.ErrorIsNil) 5362 err = unit.EnsureDead() 5363 c.Assert(err, jc.ErrorIsNil) 5364 err = unit.Remove() 5365 c.Assert(err, jc.ErrorIsNil) 5366 5367 err = s.app.Destroy() 5368 c.Assert(err, jc.ErrorIsNil) 5369 c.Assert(s.app.Life(), gc.Equals, state.Dead) 5370 // App not removed since cluster resources not cleaned up yet. 5371 assertLife(c, s.app, state.Dead) 5372 } 5373 5374 func (s *CAASApplicationSuite) TestDestroyStaleNonZeroUnitCount(c *gc.C) { 5375 unit, err := s.app.AddUnit(state.AddUnitParams{}) 5376 c.Assert(err, jc.ErrorIsNil) 5377 err = s.app.Refresh() 5378 c.Assert(err, jc.ErrorIsNil) 5379 err = unit.EnsureDead() 5380 c.Assert(err, jc.ErrorIsNil) 5381 err = unit.Remove() 5382 c.Assert(err, jc.ErrorIsNil) 5383 5384 err = s.app.Destroy() 5385 c.Assert(err, jc.ErrorIsNil) 5386 c.Assert(s.app.Life(), gc.Equals, state.Dead) 5387 // App not removed since cluster resources not cleaned up yet. 5388 assertLife(c, s.app, state.Dead) 5389 } 5390 5391 func (s *CAASApplicationSuite) TestDestroyStaleZeroUnitCount(c *gc.C) { 5392 unit, err := s.app.AddUnit(state.AddUnitParams{}) 5393 c.Assert(err, jc.ErrorIsNil) 5394 5395 err = s.app.Destroy() 5396 c.Assert(err, jc.ErrorIsNil) 5397 c.Assert(s.app.Life(), gc.Equals, state.Dying) 5398 assertLife(c, s.app, state.Dying) 5399 5400 err = unit.EnsureDead() 5401 c.Assert(err, jc.ErrorIsNil) 5402 assertLife(c, s.app, state.Dying) 5403 5404 c.Assert(unit.Remove(), jc.ErrorIsNil) 5405 assertCleanupCount(c, s.caasSt, 1) 5406 c.Assert(err, jc.ErrorIsNil) 5407 // App not removed since cluster resources not cleaned up yet. 5408 assertLife(c, s.app, state.Dead) 5409 } 5410 5411 func (s *CAASApplicationSuite) TestDestroyWithRemovableRelation(c *gc.C) { 5412 ch := state.AddTestingCharmForSeries(c, s.caasSt, "kubernetes", "mysql") 5413 mysql := state.AddTestingApplicationForBase(c, s.caasSt, state.UbuntuBase("20.04"), "mysql", ch) 5414 eps, err := s.caasSt.InferEndpoints("gitlab", "mysql") 5415 c.Assert(err, jc.ErrorIsNil) 5416 rel, err := s.caasSt.AddRelation(eps...) 5417 c.Assert(err, jc.ErrorIsNil) 5418 5419 // Destroy a application with no units in relation scope; check application and 5420 // unit removed. 5421 err = mysql.Destroy() 5422 c.Assert(err, jc.ErrorIsNil) 5423 err = mysql.Refresh() 5424 c.Assert(err, jc.ErrorIsNil) 5425 // App not removed since cluster resources not cleaned up yet. 5426 assertLife(c, mysql, state.Dead) 5427 5428 err = rel.Refresh() 5429 c.Assert(err, jc.Satisfies, errors.IsNotFound) 5430 } 5431 5432 func (s *CAASApplicationSuite) TestDestroyWithReferencedRelation(c *gc.C) { 5433 s.assertDestroyWithReferencedRelation(c, true) 5434 } 5435 5436 func (s *CAASApplicationSuite) TestDestroyWithReferencedRelationStaleCount(c *gc.C) { 5437 s.assertDestroyWithReferencedRelation(c, false) 5438 } 5439 5440 func (s *CAASApplicationSuite) assertDestroyWithReferencedRelation(c *gc.C, refresh bool) { 5441 ch := state.AddTestingCharmForSeries(c, s.caasSt, "kubernetes", "mysql") 5442 mysql := state.AddTestingApplicationForBase(c, s.caasSt, state.UbuntuBase("20.04"), "mysql", ch) 5443 eps, err := s.caasSt.InferEndpoints("gitlab", "mysql") 5444 c.Assert(err, jc.ErrorIsNil) 5445 rel0, err := s.caasSt.AddRelation(eps...) 5446 c.Assert(err, jc.ErrorIsNil) 5447 5448 ch = state.AddTestingCharmForSeries(c, s.caasSt, "kubernetes", "proxy") 5449 state.AddTestingApplicationForBase(c, s.caasSt, state.UbuntuBase("20.04"), "proxy", ch) 5450 eps, err = s.caasSt.InferEndpoints("proxy", "gitlab") 5451 c.Assert(err, jc.ErrorIsNil) 5452 rel1, err := s.caasSt.AddRelation(eps...) 5453 c.Assert(err, jc.ErrorIsNil) 5454 5455 // Add a separate reference to the first relation. 5456 unit, err := mysql.AddUnit(state.AddUnitParams{}) 5457 c.Assert(err, jc.ErrorIsNil) 5458 ru, err := rel0.Unit(unit) 5459 c.Assert(err, jc.ErrorIsNil) 5460 err = ru.EnterScope(nil) 5461 c.Assert(err, jc.ErrorIsNil) 5462 5463 // Optionally update the application document to get correct relation counts. 5464 if refresh { 5465 err = s.app.Destroy() 5466 c.Assert(err, jc.ErrorIsNil) 5467 } 5468 5469 // Destroy, and check that the first relation becomes Dying... 5470 c.Assert(s.app.Destroy(), jc.ErrorIsNil) 5471 assertLife(c, rel0, state.Dying) 5472 5473 // ...while the second is removed directly. 5474 err = rel1.Refresh() 5475 c.Assert(err, jc.Satisfies, errors.IsNotFound) 5476 5477 // Drop the last reference to the first relation; check the relation and 5478 // the application are are both removed. 5479 c.Assert(ru.LeaveScope(), jc.ErrorIsNil) 5480 assertCleanupCount(c, s.caasSt, 1) 5481 // App not removed since cluster resources not cleaned up yet. 5482 assertLife(c, s.app, state.Dead) 5483 5484 err = rel0.Refresh() 5485 c.Assert(err, jc.Satisfies, errors.IsNotFound) 5486 } 5487 5488 func (s *CAASApplicationSuite) TestDestroyQueuesUnitCleanup(c *gc.C) { 5489 // Add 5 units; block quick-remove of gitlab/1 and gitlab/3 5490 units := make([]*state.Unit, 5) 5491 for i := range units { 5492 unit, err := s.app.AddUnit(state.AddUnitParams{}) 5493 c.Assert(err, jc.ErrorIsNil) 5494 units[i] = unit 5495 if i%2 != 0 { 5496 unitState := state.NewUnitState() 5497 unitState.SetUniterState("idle") 5498 err := unit.SetState(unitState, state.UnitStateSizeLimits{}) 5499 c.Assert(err, jc.ErrorIsNil) 5500 } 5501 } 5502 5503 assertDoesNotNeedCleanup(c, s.caasSt) 5504 5505 // Destroy gitlab, and check units are not touched. 5506 err := s.app.Destroy() 5507 c.Assert(err, jc.ErrorIsNil) 5508 assertLife(c, s.app, state.Dying) 5509 for _, unit := range units { 5510 assertLife(c, unit, state.Alive) 5511 } 5512 5513 dirty, err := s.caasSt.NeedsCleanup() 5514 c.Assert(err, jc.ErrorIsNil) 5515 c.Assert(dirty, jc.IsTrue) 5516 assertCleanupCount(c, s.caasSt, 2) 5517 5518 for i, unit := range units { 5519 if i%2 != 0 { 5520 assertLife(c, unit, state.Dying) 5521 } else { 5522 assertRemoved(c, unit) 5523 } 5524 } 5525 5526 // App dying until units are gone. 5527 assertLife(c, s.app, state.Dying) 5528 } 5529 5530 func (s *ApplicationSuite) TestSetOperatorStatusNonCAAS(c *gc.C) { 5531 _, err := state.ApplicationOperatorStatus(s.State, s.mysql.Name()) 5532 c.Assert(err, jc.Satisfies, errors.IsNotFound) 5533 } 5534 5535 func (s *ApplicationSuite) TestSetOperatorStatus(c *gc.C) { 5536 st := s.Factory.MakeModel(c, &factory.ModelParams{ 5537 Name: "caas-model", 5538 Type: state.ModelTypeCAAS, 5539 }) 5540 defer st.Close() 5541 f := factory.NewFactory(st, s.StatePool) 5542 ch := f.MakeCharm(c, &factory.CharmParams{Name: "gitlab", Series: "kubernetes"}) 5543 app := f.MakeApplication(c, &factory.ApplicationParams{Name: "gitlab", Charm: ch}) 5544 5545 now := coretesting.ZeroTime() 5546 sInfo := status.StatusInfo{ 5547 Status: status.Error, 5548 Message: "broken", 5549 Since: &now, 5550 } 5551 err := app.SetOperatorStatus(sInfo) 5552 c.Assert(err, jc.ErrorIsNil) 5553 5554 appStatus, err := state.ApplicationOperatorStatus(st, app.Name()) 5555 c.Assert(err, jc.ErrorIsNil) 5556 c.Assert(appStatus.Status, gc.DeepEquals, status.Error) 5557 c.Assert(appStatus.Message, gc.DeepEquals, "broken") 5558 } 5559 5560 func (s *ApplicationSuite) TestCharmLegacyOnlySupportsOneSeries(c *gc.C) { 5561 ch := state.AddTestingCharmForSeries(c, s.State, "precise", "mysql") 5562 app := s.AddTestingApplication(c, "legacy-charm", ch) 5563 err := app.VerifySupportedBase(state.UbuntuBase("12.10")) 5564 c.Assert(err, jc.ErrorIsNil) 5565 err = app.VerifySupportedBase(state.UbuntuBase("16.04")) 5566 c.Assert(err, gc.ErrorMatches, "base \"ubuntu@16.04\" not supported by charm, the charm supported bases are: ubuntu@12.10") 5567 } 5568 5569 func (s *ApplicationSuite) TestCharmLegacyNoOSInvalid(c *gc.C) { 5570 ch := state.AddTestingCharmForSeries(c, s.State, "precise", "sample-fail-no-os") 5571 _, err := s.State.AddApplication(state.AddApplicationArgs{ 5572 Name: "sample-fail-no-os", 5573 Charm: ch, 5574 CharmOrigin: &state.CharmOrigin{ 5575 Source: "charm-hub", 5576 Platform: &state.Platform{ 5577 OS: "ubuntu", 5578 Channel: "22.04/stable", 5579 }, 5580 }, 5581 }) 5582 c.Assert(err, gc.ErrorMatches, `.*charm does not define any bases`) 5583 } 5584 5585 func (s *ApplicationSuite) TestDeployedMachines(c *gc.C) { 5586 charm := s.Factory.MakeCharm(c, &factory.CharmParams{Name: "riak"}) 5587 app := s.Factory.MakeApplication(c, &factory.ApplicationParams{Charm: charm}) 5588 s.Factory.MakeUnit(c, &factory.UnitParams{Application: app}) 5589 machines, err := app.DeployedMachines() 5590 5591 c.Assert(err, jc.ErrorIsNil) 5592 var ids []string 5593 for _, m := range machines { 5594 ids = append(ids, m.Id()) 5595 } 5596 c.Assert(ids, jc.SameContents, []string{"0"}) 5597 } 5598 5599 func (s *ApplicationSuite) TestDeployedMachinesNotAssignedUnit(c *gc.C) { 5600 charm := s.Factory.MakeCharm(c, &factory.CharmParams{Name: "riak"}) 5601 app := s.Factory.MakeApplication(c, &factory.ApplicationParams{Charm: charm}) 5602 5603 unit, err := app.AddUnit(state.AddUnitParams{}) 5604 c.Assert(err, jc.ErrorIsNil) 5605 _, err = unit.AssignedMachineId() 5606 c.Assert(err, jc.Satisfies, errors.IsNotAssigned) 5607 5608 machines, err := app.DeployedMachines() 5609 c.Assert(err, jc.ErrorIsNil) 5610 c.Assert(machines, gc.HasLen, 0) 5611 } 5612 5613 func (s *ApplicationSuite) TestCAASSidecarCharm(c *gc.C) { 5614 st, app := s.addCAASSidecarApplication(c) 5615 defer st.Close() 5616 unit, err := app.AddUnit(state.AddUnitParams{}) 5617 c.Assert(err, jc.ErrorIsNil) 5618 sidecar, err := unit.IsSidecar() 5619 c.Assert(err, jc.ErrorIsNil) 5620 c.Assert(sidecar, jc.IsTrue) 5621 } 5622 5623 func (s *ApplicationSuite) addCAASSidecarApplication(c *gc.C) (*state.State, *state.Application) { 5624 st := s.Factory.MakeModel(c, &factory.ModelParams{ 5625 Name: "caas-model", 5626 Type: state.ModelTypeCAAS, 5627 }) 5628 f := factory.NewFactory(st, s.StatePool) 5629 5630 charmDef := ` 5631 name: cockroachdb 5632 description: foo 5633 summary: foo 5634 containers: 5635 redis: 5636 resource: redis-container-resource 5637 resources: 5638 redis-container-resource: 5639 name: redis-container 5640 type: oci-image 5641 provides: 5642 data-port: 5643 interface: data 5644 scope: container 5645 ` 5646 ch := state.AddCustomCharmWithManifest(c, st, "cockroach", "metadata.yaml", charmDef, "focal", 1) 5647 return st, f.MakeApplication(c, &factory.ApplicationParams{Name: "cockroachdb", Charm: ch}) 5648 } 5649 5650 func (s *ApplicationSuite) TestCAASNonSidecarCharm(c *gc.C) { 5651 st := s.Factory.MakeModel(c, &factory.ModelParams{ 5652 Name: "caas-model", 5653 Type: state.ModelTypeCAAS, 5654 }) 5655 defer st.Close() 5656 f := factory.NewFactory(st, s.StatePool) 5657 5658 charmDef := ` 5659 name: mysql 5660 description: foo 5661 summary: foo 5662 series: 5663 - kubernetes 5664 deployment: 5665 mode: workload 5666 ` 5667 ch := state.AddCustomCharmForSeries(c, st, "mysql", "metadata.yaml", charmDef, "kubernetes", 1) 5668 app := f.MakeApplication(c, &factory.ApplicationParams{Name: "mysql", Charm: ch}) 5669 5670 unit, err := app.AddUnit(state.AddUnitParams{}) 5671 c.Assert(err, jc.ErrorIsNil) 5672 sidecar, err := unit.IsSidecar() 5673 c.Assert(err, jc.ErrorIsNil) 5674 c.Assert(sidecar, jc.IsFalse) 5675 } 5676 5677 func (s *ApplicationSuite) TestWatchApplicationsWithPendingCharms(c *gc.C) { 5678 w := s.State.WatchApplicationsWithPendingCharms() 5679 defer func() { _ = w.Stop() }() 5680 5681 wc := statetesting.NewStringsWatcherC(c, w) 5682 wc.AssertChange() // consume initial change set. 5683 5684 // Add a pending charm with an origin and associate it with the 5685 // application. This should trigger a change. 5686 dummy2 := s.dummyCharm(c, "ch:dummy-1") 5687 dummy2.SHA256 = "" // indicates that we don't have the data in the blobstore yet. 5688 dummy2.StoragePath = "" // indicates that we don't have the data in the blobstore yet. 5689 ch2, err := s.State.AddCharmMetadata(dummy2) 5690 c.Assert(err, jc.ErrorIsNil) 5691 twoOrigin := defaultCharmOrigin(ch2.URL()) 5692 twoOrigin.Platform.OS = "ubuntu" 5693 twoOrigin.Platform.Channel = "22.04/stable" 5694 err = s.mysql.SetCharm(state.SetCharmConfig{ 5695 Charm: ch2, 5696 CharmOrigin: twoOrigin, 5697 }) 5698 c.Assert(err, jc.ErrorIsNil) 5699 wc.AssertChange(s.mysql.Name()) 5700 5701 // "Upload" a charm and check that we don't get a notification for it. 5702 dummy3 := s.dummyCharm(c, "ch:dummy-2") 5703 ch3, err := s.State.AddCharm(dummy3) 5704 c.Assert(err, jc.ErrorIsNil) 5705 threeOrigin := defaultCharmOrigin(ch3.URL()) 5706 threeOrigin.Platform.OS = "ubuntu" 5707 threeOrigin.Platform.Channel = "22.04/stable" 5708 threeOrigin.ID = "charm-hub-id" 5709 threeOrigin.Hash = "charm-hub-hash" 5710 err = s.mysql.SetCharm(state.SetCharmConfig{ 5711 Charm: ch3, 5712 CharmOrigin: threeOrigin, 5713 }) 5714 c.Assert(err, jc.ErrorIsNil) 5715 wc.AssertNoChange() 5716 origin := &state.CharmOrigin{ 5717 Source: "charm-hub", 5718 Platform: &state.Platform{ 5719 OS: "ubuntu", 5720 Channel: "22.04/stable", 5721 }, 5722 } 5723 // Simulate a bundle deploying multiple applications from a single 5724 // charm. The watcher needs to notify on the secondary applications. 5725 appSameCharm, err := s.State.AddApplication(state.AddApplicationArgs{ 5726 Name: "mysql-testing", 5727 Charm: ch3, 5728 CharmOrigin: origin, 5729 }) 5730 c.Assert(err, jc.ErrorIsNil) 5731 wc.AssertChange(appSameCharm.Name()) 5732 origin.ID = "charm-hub-id" 5733 origin.Hash = "charm-hub-hash" 5734 _ = appSameCharm.SetCharm(state.SetCharmConfig{ 5735 Charm: ch3, 5736 CharmOrigin: origin, 5737 }) 5738 c.Assert(err, jc.ErrorIsNil) 5739 wc.AssertNoChange() 5740 } 5741 5742 func (s *ApplicationSuite) dummyCharm(c *gc.C, curlOverride string) state.CharmInfo { 5743 info := state.CharmInfo{ 5744 Charm: testcharms.Repo.CharmDir("dummy"), 5745 StoragePath: "dummy-1", 5746 SHA256: "dummy-1-sha256", 5747 Version: "dummy-146-g725cfd3-dirty", 5748 } 5749 if curlOverride != "" { 5750 info.ID = curlOverride 5751 } else { 5752 info.ID = fmt.Sprintf("local:quantal/%s-%d", info.Charm.Meta().Name, info.Charm.Revision()) 5753 } 5754 info.Charm.Meta().Series = []string{"quantal", "jammy"} 5755 return info 5756 } 5757 5758 func (s *ApplicationSuite) TestWatch(c *gc.C) { 5759 w := s.mysql.WatchConfigSettingsHash() 5760 defer testing.AssertStop(c, w) 5761 5762 wc := testing.NewStringsWatcherC(c, w) 5763 wc.AssertChange("1e11259677ef769e0ec4076b873c76dcc3a54be7bc651b081d0f0e2b87077717") 5764 5765 schema := environschema.Fields{ 5766 "username": environschema.Attr{Type: environschema.Tstring}, 5767 "alive": environschema.Attr{Type: environschema.Tbool}, 5768 "skill-level": environschema.Attr{Type: environschema.Tint}, 5769 "options": environschema.Attr{Type: environschema.Tattrs}, 5770 } 5771 5772 err := s.mysql.UpdateApplicationConfig(config.ConfigAttributes{ 5773 "username": "abbas", 5774 "alive": true, 5775 "skill-level": 23, 5776 "options": map[string]string{ 5777 "fortuna": "crescis", 5778 "luna": "velut", 5779 "status": "malus", 5780 }, 5781 }, nil, schema, nil) 5782 c.Assert(err, jc.ErrorIsNil) 5783 5784 wc.AssertChange("e1471e8a7299da0ac2150445ffc6d08d9d801194037d88416c54b01899b8a9b2") 5785 } 5786 5787 func (s *ApplicationSuite) TestProvisioningState(c *gc.C) { 5788 ps := s.mysql.ProvisioningState() 5789 c.Assert(ps, gc.IsNil) 5790 5791 err := s.mysql.SetProvisioningState(state.ApplicationProvisioningState{ 5792 Scaling: true, 5793 ScaleTarget: 10, 5794 }) 5795 c.Assert(errors.Is(err, stateerrors.ProvisioningStateInconsistent), jc.IsTrue) 5796 5797 err = s.mysql.SetScale(10, 0, true) 5798 c.Assert(err, jc.ErrorIsNil) 5799 5800 err = s.mysql.SetProvisioningState(state.ApplicationProvisioningState{ 5801 Scaling: true, 5802 ScaleTarget: 10, 5803 }) 5804 c.Assert(err, jc.ErrorIsNil) 5805 5806 ps = s.mysql.ProvisioningState() 5807 c.Assert(ps, jc.DeepEquals, &state.ApplicationProvisioningState{ 5808 Scaling: true, 5809 ScaleTarget: 10, 5810 }) 5811 } 5812 5813 func (s *CAASApplicationSuite) TestUpsertCAASUnit(c *gc.C) { 5814 registry := &storage.StaticProviderRegistry{ 5815 Providers: map[storage.ProviderType]storage.Provider{ 5816 "kubernetes": &dummy.StorageProvider{ 5817 StorageScope: storage.ScopeEnviron, 5818 IsDynamic: true, 5819 IsReleasable: true, 5820 SupportsFunc: func(k storage.StorageKind) bool { 5821 return k == storage.StorageKindBlock 5822 }, 5823 }, 5824 }, 5825 } 5826 5827 st := s.Factory.MakeCAASModel(c, &factory.ModelParams{ 5828 CloudName: "caascloud", 5829 }) 5830 s.AddCleanup(func(_ *gc.C) { _ = st.Close() }) 5831 5832 pm := poolmanager.New(state.NewStateSettings(st), registry) 5833 _, err := pm.Create("kubernetes", "kubernetes", map[string]interface{}{}) 5834 c.Assert(err, jc.ErrorIsNil) 5835 s.policy = testing.MockPolicy{ 5836 GetStorageProviderRegistry: func() (storage.ProviderRegistry, error) { 5837 return registry, nil 5838 }, 5839 } 5840 5841 sb, err := state.NewStorageBackend(st) 5842 c.Assert(err, jc.ErrorIsNil) 5843 5844 fsInfo := state.FilesystemInfo{ 5845 Size: 100, 5846 Pool: "kubernetes", 5847 } 5848 volumeInfo := state.VolumeInfo{ 5849 VolumeId: "pv-database-0", 5850 Size: 100, 5851 Pool: "kubernetes", 5852 Persistent: true, 5853 } 5854 storageTag, err := sb.AddExistingFilesystem(fsInfo, &volumeInfo, "database") 5855 c.Assert(err, jc.ErrorIsNil) 5856 c.Assert(storageTag.Id(), gc.Equals, "database/0") 5857 5858 ch := state.AddTestingCharmForSeries(c, st, "quantal", "cockroachdb") 5859 cockroachdb := state.AddTestingApplicationWithStorage(c, st, "cockroachdb", ch, map[string]state.StorageConstraints{ 5860 "database": { 5861 Pool: "kubernetes", 5862 Size: 100, 5863 Count: 0, 5864 }, 5865 }) 5866 5867 unitName := "cockroachdb/0" 5868 providerId := "cockroachdb-0" 5869 address := "1.2.3.4" 5870 ports := []string{"80", "443"} 5871 5872 // output of utils.AgentPasswordHash("juju") 5873 passwordHash := "v+jK3ht5NEdKeoQBfyxmlYe0" 5874 5875 p := state.UpsertCAASUnitParams{ 5876 AddUnitParams: state.AddUnitParams{ 5877 UnitName: &unitName, 5878 ProviderId: &providerId, 5879 Address: &address, 5880 Ports: &ports, 5881 PasswordHash: &passwordHash, 5882 }, 5883 OrderedScale: true, 5884 OrderedId: 0, 5885 ObservedAttachedVolumeIDs: []string{"pv-database-0"}, 5886 } 5887 unit, err := cockroachdb.UpsertCAASUnit(p) 5888 c.Assert(err, gc.ErrorMatches, `unrequired unit cockroachdb/0 is not assigned`) 5889 c.Assert(unit, gc.IsNil) 5890 5891 err = cockroachdb.SetScale(1, 0, true) 5892 c.Assert(err, jc.ErrorIsNil) 5893 5894 unit, err = cockroachdb.UpsertCAASUnit(p) 5895 c.Assert(err, jc.ErrorIsNil) 5896 c.Assert(unit, gc.NotNil) 5897 c.Assert(unit.UnitTag().Id(), gc.Equals, "cockroachdb/0") 5898 c.Assert(unit.Life(), gc.Equals, state.Alive) 5899 containerInfo, err := unit.ContainerInfo() 5900 c.Assert(err, jc.ErrorIsNil) 5901 c.Assert(containerInfo.ProviderId(), gc.Equals, "cockroachdb-0") 5902 c.Assert(containerInfo.Ports(), jc.SameContents, []string{"80", "443"}) 5903 c.Assert(containerInfo.Address().Value, gc.Equals, "1.2.3.4") 5904 5905 err = unit.Destroy() 5906 c.Assert(err, jc.ErrorIsNil) 5907 5908 err = sb.DetachStorage(storageTag, unit.UnitTag(), false, 0) 5909 c.Assert(err, jc.ErrorIsNil) 5910 5911 err = sb.DetachFilesystem(unit.UnitTag(), names.NewFilesystemTag("0")) 5912 c.Assert(err, jc.ErrorIsNil) 5913 err = sb.RemoveFilesystemAttachment(unit.UnitTag(), names.NewFilesystemTag("0"), false) 5914 c.Assert(err, jc.ErrorIsNil) 5915 5916 err = sb.DetachVolume(unit.Tag(), names.NewVolumeTag("0"), false) 5917 c.Assert(err, jc.ErrorIsNil) 5918 err = sb.RemoveVolumeAttachment(unit.Tag(), names.NewVolumeTag("0"), false) 5919 c.Assert(err, jc.ErrorIsNil) 5920 5921 err = unit.EnsureDead() 5922 c.Assert(err, jc.ErrorIsNil) 5923 5924 unit2, err := cockroachdb.UpsertCAASUnit(p) 5925 c.Assert(err, gc.ErrorMatches, `dead unit "cockroachdb/0" already exists`) 5926 c.Assert(unit2, gc.IsNil) 5927 5928 err = unit.Remove() 5929 c.Assert(err, jc.ErrorIsNil) 5930 5931 err = st.Cleanup() 5932 c.Assert(err, jc.ErrorIsNil) 5933 5934 unit, err = cockroachdb.UpsertCAASUnit(p) 5935 c.Assert(err, jc.ErrorIsNil) 5936 c.Assert(unit, gc.NotNil) 5937 c.Assert(unit.UnitTag().Id(), gc.Equals, "cockroachdb/0") 5938 c.Assert(unit.Life(), gc.Equals, state.Alive) 5939 containerInfo, err = unit.ContainerInfo() 5940 c.Assert(err, jc.ErrorIsNil) 5941 c.Assert(containerInfo.ProviderId(), gc.Equals, "cockroachdb-0") 5942 c.Assert(containerInfo.Ports(), jc.SameContents, []string{"80", "443"}) 5943 c.Assert(containerInfo.Address().Value, gc.Equals, "1.2.3.4") 5944 } 5945 5946 func intPtr(val int) *int { 5947 return &val 5948 } 5949 5950 func defaultCharmOrigin(curlStr string) *state.CharmOrigin { 5951 // Use ParseURL here in test until either the charm and/or application 5952 // can easily provide the same data. 5953 curl, _ := charm.ParseURL(curlStr) 5954 var source string 5955 var channel *state.Channel 5956 if charm.CharmHub.Matches(curl.Schema) { 5957 source = corecharm.CharmHub.String() 5958 channel = &state.Channel{ 5959 Risk: "stable", 5960 } 5961 } else if charm.Local.Matches(curl.Schema) { 5962 source = corecharm.Local.String() 5963 } 5964 5965 base, _ := corebase.GetBaseFromSeries(curl.Series) 5966 5967 platform := &state.Platform{ 5968 Architecture: corearch.DefaultArchitecture, 5969 OS: base.OS, 5970 Channel: base.Channel.String(), 5971 } 5972 5973 return &state.CharmOrigin{ 5974 Source: source, 5975 Type: "charm", 5976 Revision: intPtr(curl.Revision), 5977 Channel: channel, 5978 Platform: platform, 5979 } 5980 }