github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/state/relation_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 "time" 9 10 "github.com/juju/charm/v12" 11 "github.com/juju/errors" 12 "github.com/juju/names/v5" 13 jc "github.com/juju/testing/checkers" 14 "github.com/juju/utils/v3" 15 gc "gopkg.in/check.v1" 16 17 "github.com/juju/juju/core/crossmodel" 18 "github.com/juju/juju/core/permission" 19 "github.com/juju/juju/core/secrets" 20 "github.com/juju/juju/core/status" 21 "github.com/juju/juju/state" 22 "github.com/juju/juju/state/testing" 23 coretesting "github.com/juju/juju/testing" 24 "github.com/juju/juju/testing/factory" 25 ) 26 27 type RelationSuite struct { 28 ConnSuite 29 } 30 31 var _ = gc.Suite(&RelationSuite{}) 32 33 func (s *RelationSuite) TestAddRelationErrors(c *gc.C) { 34 wordpress := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 35 wordpressEP, err := wordpress.Endpoint("db") 36 c.Assert(err, jc.ErrorIsNil) 37 mysql := s.AddTestingApplication(c, "mysql", s.AddTestingCharm(c, "mysql")) 38 mysqlEP, err := mysql.Endpoint("server") 39 c.Assert(err, jc.ErrorIsNil) 40 riak := s.AddTestingApplication(c, "riak", s.AddTestingCharm(c, "riak")) 41 riakEP, err := riak.Endpoint("ring") 42 c.Assert(err, jc.ErrorIsNil) 43 44 // Check we can't add a relation with application that don't exist. 45 yoursqlEP := mysqlEP 46 yoursqlEP.ApplicationName = "yoursql" 47 _, err = s.State.AddRelation(yoursqlEP, wordpressEP) 48 c.Assert(err, gc.ErrorMatches, `cannot add relation "wordpress:db yoursql:server": application "yoursql" does not exist`) 49 assertNoRelations(c, wordpress) 50 assertNoRelations(c, mysql) 51 52 // Check that interfaces have to match. 53 msep3 := mysqlEP 54 msep3.Interface = "roflcopter" 55 _, err = s.State.AddRelation(msep3, wordpressEP) 56 c.Assert(err, gc.ErrorMatches, `cannot add relation "wordpress:db mysql:server": endpoints do not relate`) 57 assertNoRelations(c, wordpress) 58 assertNoRelations(c, mysql) 59 60 // Check a variety of surprising endpoint combinations. 61 _, err = s.State.AddRelation(wordpressEP) 62 c.Assert(err, gc.ErrorMatches, `cannot add relation "wordpress:db": relation must have two endpoints`) 63 assertNoRelations(c, wordpress) 64 65 _, err = s.State.AddRelation(riakEP, wordpressEP) 66 c.Assert(err, gc.ErrorMatches, `cannot add relation "wordpress:db riak:ring": endpoints do not relate`) 67 assertOneRelation(c, riak, 0, riakEP) 68 assertNoRelations(c, wordpress) 69 70 _, err = s.State.AddRelation(riakEP, riakEP) 71 c.Assert(err, gc.ErrorMatches, `cannot add relation "riak:ring riak:ring": endpoints do not relate`) 72 assertOneRelation(c, riak, 0, riakEP) 73 74 _, err = s.State.AddRelation() 75 c.Assert(err, gc.ErrorMatches, `cannot add relation "": relation must have two endpoints`) 76 _, err = s.State.AddRelation(mysqlEP, wordpressEP, riakEP) 77 c.Assert(err, gc.ErrorMatches, `cannot add relation "wordpress:db mysql:server riak:ring": relation must have two endpoints`) 78 assertOneRelation(c, riak, 0, riakEP) 79 assertNoRelations(c, wordpress) 80 assertNoRelations(c, mysql) 81 82 // Check that a relation can't be added to a Dying application. 83 _, err = wordpress.AddUnit(state.AddUnitParams{}) 84 c.Assert(err, jc.ErrorIsNil) 85 err = wordpress.Destroy() 86 c.Assert(err, jc.ErrorIsNil) 87 _, err = s.State.AddRelation(mysqlEP, wordpressEP) 88 c.Assert(err, gc.ErrorMatches, `cannot add relation "wordpress:db mysql:server": application "wordpress" is not alive`) 89 assertNoRelations(c, wordpress) 90 assertNoRelations(c, mysql) 91 } 92 93 func (s *StateSuite) TestAddRelationWithMaxLimit(c *gc.C) { 94 s.AddTestingApplication(c, "mysql", s.AddTestingCharm(c, "mysql")) 95 s.AddTestingApplication(c, "mariadb", s.AddTestingCharm(c, "mariadb")) 96 s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 97 98 // First relation should be established without an issue 99 eps, err := s.State.InferEndpoints("wordpress", "mysql") 100 c.Assert(err, jc.ErrorIsNil) 101 _, err = s.State.AddRelation(eps...) 102 c.Assert(err, jc.ErrorIsNil) 103 104 // Attempting to add a new relation between wordpress and mariadb 105 // should fail because wordpress:db specifies a max relation limit of 1 106 eps, err = s.State.InferEndpoints("wordpress", "mariadb") 107 c.Assert(err, jc.ErrorIsNil) 108 _, err = s.State.AddRelation(eps...) 109 c.Assert(err, jc.Satisfies, errors.IsQuotaLimitExceeded, gc.Commentf("expected second AddRelation attempt to fail due to the limit:1 entry in the wordpress charm's metadata.yaml")) 110 } 111 112 func (s *RelationSuite) TestRetrieveSuccess(c *gc.C) { 113 wordpress := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 114 wordpressEP, err := wordpress.Endpoint("db") 115 c.Assert(err, jc.ErrorIsNil) 116 mysql := s.AddTestingApplication(c, "mysql", s.AddTestingCharm(c, "mysql")) 117 mysqlUnit, err := mysql.AddUnit(state.AddUnitParams{}) 118 c.Assert(err, jc.ErrorIsNil) 119 mysqlEP, err := mysql.Endpoint("server") 120 c.Assert(err, jc.ErrorIsNil) 121 expect, err := s.State.AddRelation(wordpressEP, mysqlEP) 122 c.Assert(err, jc.ErrorIsNil) 123 mysqlru, err := expect.Unit(mysqlUnit) 124 c.Assert(err, jc.ErrorIsNil) 125 err = mysqlru.EnterScope(nil) 126 c.Assert(err, jc.ErrorIsNil) 127 128 rel, err := s.State.EndpointsRelation(wordpressEP, mysqlEP) 129 check := func() { 130 c.Assert(err, jc.ErrorIsNil) 131 c.Assert(rel.Id(), gc.Equals, expect.Id()) 132 c.Assert(rel.String(), gc.Equals, expect.String()) 133 c.Assert(rel.UnitCount(), gc.Equals, 1) 134 } 135 check() 136 rel, err = s.State.EndpointsRelation(mysqlEP, wordpressEP) 137 check() 138 rel, err = s.State.Relation(expect.Id()) 139 check() 140 } 141 142 func (s *RelationSuite) TestRetrieveNotFound(c *gc.C) { 143 subway := state.Endpoint{ 144 ApplicationName: "subway", 145 Relation: charm.Relation{ 146 Name: "db", 147 Interface: "mongodb", 148 Role: charm.RoleRequirer, 149 Scope: charm.ScopeGlobal, 150 }, 151 } 152 mongo := state.Endpoint{ 153 ApplicationName: "mongo", 154 Relation: charm.Relation{ 155 Name: "server", 156 Interface: "mongodb", 157 Role: charm.RoleProvider, 158 Scope: charm.ScopeGlobal, 159 }, 160 } 161 _, err := s.State.EndpointsRelation(subway, mongo) 162 c.Assert(err, gc.ErrorMatches, `relation "subway:db mongo:server" not found`) 163 c.Assert(err, jc.Satisfies, errors.IsNotFound) 164 165 _, err = s.State.Relation(999) 166 c.Assert(err, gc.ErrorMatches, `relation 999 not found`) 167 c.Assert(err, jc.Satisfies, errors.IsNotFound) 168 } 169 170 func (s *RelationSuite) TestAddRelation(c *gc.C) { 171 // Add a relation. 172 wordpress := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 173 wordpressEP, err := wordpress.Endpoint("db") 174 c.Assert(err, jc.ErrorIsNil) 175 mysql := s.AddTestingApplication(c, "mysql", s.AddTestingCharm(c, "mysql")) 176 mysqlEP, err := mysql.Endpoint("server") 177 c.Assert(err, jc.ErrorIsNil) 178 _, err = s.State.AddRelation(wordpressEP, mysqlEP) 179 c.Assert(err, jc.ErrorIsNil) 180 assertOneRelation(c, mysql, 0, mysqlEP, wordpressEP) 181 assertOneRelation(c, wordpress, 0, wordpressEP, mysqlEP) 182 183 // Check we cannot re-add the same relation, regardless of endpoint ordering. 184 _, err = s.State.AddRelation(mysqlEP, wordpressEP) 185 c.Assert(err, gc.ErrorMatches, `cannot add relation "wordpress:db mysql:server": relation wordpress:db mysql:server`) 186 _, err = s.State.AddRelation(wordpressEP, mysqlEP) 187 c.Assert(err, gc.ErrorMatches, `cannot add relation "wordpress:db mysql:server": relation wordpress:db mysql:server`) 188 assertOneRelation(c, mysql, 0, mysqlEP, wordpressEP) 189 assertOneRelation(c, wordpress, 0, wordpressEP, mysqlEP) 190 } 191 192 func (s *RelationSuite) TestAddRelationSeriesNeedNotMatch(c *gc.C) { 193 wordpress := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 194 wordpressEP, err := wordpress.Endpoint("db") 195 c.Assert(err, jc.ErrorIsNil) 196 mysql := s.AddTestingApplication(c, "mysql", s.AddSeriesCharm(c, "mysql", "bionic")) 197 mysqlEP, err := mysql.Endpoint("server") 198 c.Assert(err, jc.ErrorIsNil) 199 _, err = s.State.AddRelation(wordpressEP, mysqlEP) 200 c.Assert(err, jc.ErrorIsNil) 201 assertOneRelation(c, mysql, 0, mysqlEP, wordpressEP) 202 assertOneRelation(c, wordpress, 0, wordpressEP, mysqlEP) 203 } 204 205 func (s *RelationSuite) TestAddContainerRelation(c *gc.C) { 206 // Add a relation. 207 wordpress := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 208 wordpressEP, err := wordpress.Endpoint("juju-info") 209 c.Assert(err, jc.ErrorIsNil) 210 logging := s.AddTestingApplication(c, "logging", s.AddTestingCharm(c, "logging")) 211 loggingEP, err := logging.Endpoint("info") 212 c.Assert(err, jc.ErrorIsNil) 213 _, err = s.State.AddRelation(wordpressEP, loggingEP) 214 c.Assert(err, jc.ErrorIsNil) 215 216 // Check that the endpoints both have container scope. 217 wordpressEP.Scope = charm.ScopeContainer 218 assertOneRelation(c, logging, 0, loggingEP, wordpressEP) 219 assertOneRelation(c, wordpress, 0, wordpressEP, loggingEP) 220 221 // Check we cannot re-add the same relation, regardless of endpoint ordering. 222 _, err = s.State.AddRelation(loggingEP, wordpressEP) 223 c.Assert(err, gc.ErrorMatches, `cannot add relation "logging:info wordpress:juju-info": relation logging:info wordpress:juju-info`) 224 _, err = s.State.AddRelation(wordpressEP, loggingEP) 225 c.Assert(err, gc.ErrorMatches, `cannot add relation "logging:info wordpress:juju-info": relation logging:info wordpress:juju-info`) 226 assertOneRelation(c, logging, 0, loggingEP, wordpressEP) 227 assertOneRelation(c, wordpress, 0, wordpressEP, loggingEP) 228 } 229 230 func (s *RelationSuite) TestAddContainerRelationSeriesMustMatch(c *gc.C) { 231 wordpress := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 232 wordpressEP, err := wordpress.Endpoint("juju-info") 233 c.Assert(err, jc.ErrorIsNil) 234 logging := s.AddTestingApplication(c, "logging", s.AddSeriesCharm(c, "logging-raring", "bionic")) 235 loggingEP, err := logging.Endpoint("info") 236 c.Assert(err, jc.ErrorIsNil) 237 _, err = s.State.AddRelation(wordpressEP, loggingEP) 238 c.Assert(err, gc.ErrorMatches, `cannot add relation "logging:info wordpress:juju-info": principal and subordinate applications' bases must match`) 239 } 240 241 func (s *RelationSuite) TestAddContainerRelationMultiSeriesMatch(c *gc.C) { 242 principal := s.AddTestingApplication(c, "multi-series", s.AddSeriesCharm(c, "multi-series", "quantal")) 243 principalEP, err := principal.Endpoint("multi-directory") 244 c.Assert(err, jc.ErrorIsNil) 245 subord := s.AddTestingApplication(c, "multi-series-subordinate", s.AddSeriesCharm(c, "multi-series-subordinate", "bionic")) 246 subordEP, err := subord.Endpoint("multi-directory") 247 c.Assert(err, jc.ErrorIsNil) 248 249 _, err = s.State.AddRelation(principalEP, subordEP) 250 principalEP.Scope = charm.ScopeContainer 251 c.Assert(err, jc.ErrorIsNil) 252 assertOneRelation(c, subord, 0, subordEP, principalEP) 253 assertOneRelation(c, principal, 0, principalEP, subordEP) 254 } 255 256 func (s *RelationSuite) TestAddContainerRelationMultiSeriesNoMatch(c *gc.C) { 257 principal := s.AddTestingApplication(c, "multi-series", s.AddTestingCharm(c, "multi-series")) 258 principalEP, err := principal.Endpoint("multi-directory") 259 c.Assert(err, jc.ErrorIsNil) 260 meta := ` 261 name: multi-series-subordinate 262 summary: a test charm 263 description: a test 264 subordinate: true 265 series: 266 - xenial 267 requires: 268 multi-directory: 269 interface: logging 270 scope: container 271 `[1:] 272 subord := s.AddTestingApplication(c, "multi-series-subordinate", s.AddMetaCharm(c, "multi-series-subordinate", meta, 1)) 273 subordEP, err := subord.Endpoint("multi-directory") 274 c.Assert(err, jc.ErrorIsNil) 275 276 _, err = s.State.AddRelation(principalEP, subordEP) 277 principalEP.Scope = charm.ScopeContainer 278 c.Assert(err, gc.ErrorMatches, `cannot add relation "multi-series-subordinate:multi-directory multi-series:multi-directory": principal and subordinate applications' bases must match`) 279 } 280 281 func (s *RelationSuite) TestAddContainerRelationWithNoSubordinate(c *gc.C) { 282 wordpress := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 283 wordpressSubEP, err := wordpress.Endpoint("db") 284 c.Assert(err, jc.ErrorIsNil) 285 wordpressSubEP.Scope = charm.ScopeContainer 286 mysql := s.AddTestingApplication(c, "mysql", s.AddTestingCharm(c, "mysql")) 287 mysqlEP, err := mysql.Endpoint("server") 288 c.Assert(err, jc.ErrorIsNil) 289 290 _, err = s.State.AddRelation(mysqlEP, wordpressSubEP) 291 c.Assert(err, gc.ErrorMatches, 292 `cannot add relation "wordpress:db mysql:server": container scoped relation requires at least one subordinate application`) 293 assertNoRelations(c, wordpress) 294 assertNoRelations(c, mysql) 295 } 296 297 func (s *RelationSuite) TestAddContainerRelationWithTwoSubordinates(c *gc.C) { 298 loggingCharm := s.AddTestingCharm(c, "logging") 299 logging1 := s.AddTestingApplication(c, "logging1", loggingCharm) 300 logging1EP, err := logging1.Endpoint("juju-info") 301 c.Assert(err, jc.ErrorIsNil) 302 logging2 := s.AddTestingApplication(c, "logging2", loggingCharm) 303 logging2EP, err := logging2.Endpoint("info") 304 c.Assert(err, jc.ErrorIsNil) 305 306 _, err = s.State.AddRelation(logging1EP, logging2EP) 307 c.Assert(err, jc.ErrorIsNil) 308 // AddRelation changes the scope on the endpoint if relation is container scoped. 309 logging1EP.Scope = charm.ScopeContainer 310 assertOneRelation(c, logging1, 0, logging1EP, logging2EP) 311 assertOneRelation(c, logging2, 0, logging2EP, logging1EP) 312 } 313 314 func (s *RelationSuite) TestDestroyRelation(c *gc.C) { 315 wordpress := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 316 mysql := s.AddTestingApplication(c, "mysql", s.AddTestingCharm(c, "mysql")) 317 eps, err := s.State.InferEndpoints("wordpress", "mysql") 318 c.Assert(err, jc.ErrorIsNil) 319 rel, err := s.State.AddRelation(eps...) 320 c.Assert(err, jc.ErrorIsNil) 321 322 // Test that the relation can be destroyed. 323 err = rel.Destroy() 324 c.Assert(err, jc.ErrorIsNil) 325 err = rel.Refresh() 326 c.Assert(err, jc.Satisfies, errors.IsNotFound) 327 assertNoRelations(c, wordpress) 328 assertNoRelations(c, mysql) 329 330 // Check that a second destroy is a no-op. 331 err = rel.Destroy() 332 c.Assert(err, jc.ErrorIsNil) 333 334 // Create a new relation and check that refreshing the old does not find 335 // the new. 336 _, err = s.State.AddRelation(eps...) 337 c.Assert(err, jc.ErrorIsNil) 338 err = rel.Refresh() 339 c.Assert(err, jc.Satisfies, errors.IsNotFound) 340 } 341 342 func (s *RelationSuite) TestDestroyPeerRelation(c *gc.C) { 343 // Check that a peer relation cannot be destroyed directly. 344 riakch := s.AddTestingCharm(c, "riak") 345 riak := s.AddTestingApplication(c, "riak", riakch) 346 riakEP, err := riak.Endpoint("ring") 347 c.Assert(err, jc.ErrorIsNil) 348 rel := assertOneRelation(c, riak, 0, riakEP) 349 err = rel.Destroy() 350 c.Assert(err, gc.ErrorMatches, `cannot destroy relation "riak:ring": is a peer relation`) 351 assertOneRelation(c, riak, 0, riakEP) 352 353 // Check that it is destroyed when the application is destroyed. 354 err = riak.Destroy() 355 c.Assert(err, jc.ErrorIsNil) 356 assertNoRelations(c, riak) 357 err = rel.Refresh() 358 c.Assert(err, jc.Satisfies, errors.IsNotFound) 359 360 // Create a new application (and hence a new relation in the background); check 361 // that refreshing the old one does not accidentally get the new one. 362 newriak := s.AddTestingApplication(c, "riak", riakch) 363 assertOneRelation(c, newriak, 1, riakEP) 364 err = rel.Refresh() 365 c.Assert(err, jc.Satisfies, errors.IsNotFound) 366 } 367 368 func (s *RelationSuite) TestDestroyRelationIncorrectUnitCount(c *gc.C) { 369 prr := newProReqRelation(c, &s.ConnSuite, charm.ScopeGlobal) 370 prr.allEnterScope(c) 371 372 rel := prr.rel 373 state.RemoveUnitRelations(c, rel) 374 err := rel.Refresh() 375 c.Assert(err, jc.ErrorIsNil) 376 c.Assert(rel.UnitCount(), gc.Not(gc.Equals), 0) 377 378 _, err = rel.DestroyWithForce(false, dontWait) 379 c.Assert(err, gc.ErrorMatches, ".*unit count mismatch on relation wordpress:db mysql:server: expected 4 units in scope but got 0") 380 } 381 382 func (s *RelationSuite) assertInScope(c *gc.C, relUnit *state.RelationUnit, inScope bool) { 383 ok, err := relUnit.InScope() 384 c.Assert(err, jc.ErrorIsNil) 385 c.Assert(ok, gc.Equals, inScope) 386 } 387 388 func (s *RelationSuite) assertDestroyCrossModelRelation(c *gc.C, appStatus *status.Status) { 389 rwordpress, err := s.State.AddRemoteApplication(state.AddRemoteApplicationParams{ 390 Name: "remote-wordpress", 391 SourceModel: names.NewModelTag("source-model"), 392 OfferUUID: "offer-uuid", 393 Endpoints: []charm.Relation{{ 394 Interface: "mysql", 395 Limit: 1, 396 Name: "db", 397 Role: charm.RoleRequirer, 398 Scope: charm.ScopeGlobal, 399 }}, 400 }) 401 c.Assert(err, jc.ErrorIsNil) 402 wordpressEP, err := rwordpress.Endpoint("db") 403 c.Assert(err, jc.ErrorIsNil) 404 405 mysql := s.AddTestingApplication(c, "mysql", s.AddTestingCharm(c, "mysql")) 406 mysqlUnit, err := mysql.AddUnit(state.AddUnitParams{}) 407 c.Assert(err, jc.ErrorIsNil) 408 mysqlEP, err := mysql.Endpoint("server") 409 c.Assert(err, jc.ErrorIsNil) 410 411 rel, err := s.State.AddRelation(wordpressEP, mysqlEP) 412 c.Assert(err, jc.ErrorIsNil) 413 mysqlru, err := rel.Unit(mysqlUnit) 414 c.Assert(err, jc.ErrorIsNil) 415 err = mysqlru.EnterScope(nil) 416 c.Assert(err, jc.ErrorIsNil) 417 s.assertInScope(c, mysqlru, true) 418 419 wpru, err := rel.RemoteUnit("remote-wordpress/0") 420 c.Assert(err, jc.ErrorIsNil) 421 err = wpru.EnterScope(nil) 422 c.Assert(err, jc.ErrorIsNil) 423 s.assertInScope(c, wpru, true) 424 425 if appStatus != nil { 426 err = rwordpress.SetStatus(status.StatusInfo{Status: *appStatus}) 427 c.Assert(err, jc.ErrorIsNil) 428 } 429 430 err = rel.Refresh() 431 c.Assert(err, jc.ErrorIsNil) 432 err = rel.Destroy() 433 c.Assert(err, jc.ErrorIsNil) 434 err = rel.Refresh() 435 c.Assert(err, jc.ErrorIsNil) 436 c.Assert(rel.Life(), gc.Equals, state.Dying) 437 438 // If the remote app for the relation is terminated, any remote units are 439 // forcibly removed from scope, but not local ones. 440 s.assertInScope(c, wpru, appStatus == nil || *appStatus != status.Terminated) 441 s.assertInScope(c, mysqlru, true) 442 } 443 444 func (s *RelationSuite) TestDestroyCrossModelRelationNoAppStatus(c *gc.C) { 445 s.assertDestroyCrossModelRelation(c, nil) 446 } 447 448 func (s *RelationSuite) TestDestroyCrossModelRelationAppNotTerminated(c *gc.C) { 449 st := status.Active 450 s.assertDestroyCrossModelRelation(c, &st) 451 } 452 453 func (s *RelationSuite) TestDestroyCrossModelRelationAppTerminated(c *gc.C) { 454 st := status.Terminated 455 s.assertDestroyCrossModelRelation(c, &st) 456 } 457 458 func (s *RelationSuite) TestForceDestroyCrossModelRelationOfferSide(c *gc.C) { 459 rwordpress, err := s.State.AddRemoteApplication(state.AddRemoteApplicationParams{ 460 Name: "remote-wordpress", 461 ExternalControllerUUID: "controller-uuid", 462 SourceModel: names.NewModelTag("source-model"), 463 OfferUUID: "offer-uuid", 464 Endpoints: []charm.Relation{{ 465 Interface: "mysql", 466 Limit: 1, 467 Name: "db", 468 Role: charm.RoleRequirer, 469 Scope: charm.ScopeGlobal, 470 }}, 471 IsConsumerProxy: true, 472 }) 473 c.Assert(err, jc.ErrorIsNil) 474 rc, err := state.ControllerRefCount(s.State, "controller-uuid") 475 c.Assert(err, jc.ErrorIsNil) 476 c.Assert(rc, gc.Equals, 1) 477 wordpressEP, err := rwordpress.Endpoint("db") 478 c.Assert(err, jc.ErrorIsNil) 479 480 mysql := s.AddTestingApplication(c, "mysql", s.AddTestingCharm(c, "mysql")) 481 mysqlUnit, err := mysql.AddUnit(state.AddUnitParams{}) 482 c.Assert(err, jc.ErrorIsNil) 483 mysqlEP, err := mysql.Endpoint("server") 484 c.Assert(err, jc.ErrorIsNil) 485 486 rel, err := s.State.AddRelation(wordpressEP, mysqlEP) 487 c.Assert(err, jc.ErrorIsNil) 488 mysqlru, err := rel.Unit(mysqlUnit) 489 c.Assert(err, jc.ErrorIsNil) 490 err = mysqlru.EnterScope(nil) 491 c.Assert(err, jc.ErrorIsNil) 492 s.assertInScope(c, mysqlru, true) 493 494 wpru, err := rel.RemoteUnit("remote-wordpress/0") 495 c.Assert(err, jc.ErrorIsNil) 496 err = wpru.EnterScope(nil) 497 c.Assert(err, jc.ErrorIsNil) 498 s.assertInScope(c, wpru, true) 499 500 err = rel.Refresh() 501 c.Assert(err, jc.ErrorIsNil) 502 errs, err := rel.DestroyWithForce(true, 0) 503 c.Assert(errs, gc.HasLen, 0) 504 c.Assert(err, jc.ErrorIsNil) 505 err = rel.Refresh() 506 c.Assert(err, jc.Satisfies, errors.IsNotFound) 507 err = rwordpress.Refresh() 508 c.Assert(err, jc.Satisfies, errors.IsNotFound) 509 _, err = state.ControllerRefCount(s.State, "controller-uuid") 510 c.Assert(err, jc.Satisfies, errors.IsNotFound) 511 512 s.assertInScope(c, wpru, false) 513 s.assertInScope(c, mysqlru, true) 514 } 515 516 func (s *RelationSuite) TestIsCrossModelYup(c *gc.C) { 517 rwordpress, err := s.State.AddRemoteApplication(state.AddRemoteApplicationParams{ 518 Name: "remote-wordpress", 519 SourceModel: names.NewModelTag("source-model"), 520 IsConsumerProxy: true, 521 OfferUUID: "offer-uuid", 522 Endpoints: []charm.Relation{{ 523 Interface: "mysql", 524 Limit: 1, 525 Name: "db", 526 Role: charm.RoleRequirer, 527 Scope: charm.ScopeGlobal, 528 }}, 529 }) 530 c.Assert(err, jc.ErrorIsNil) 531 wordpressEP, err := rwordpress.Endpoint("db") 532 c.Assert(err, jc.ErrorIsNil) 533 mysql := s.AddTestingApplication(c, "mysql", s.AddTestingCharm(c, "mysql")) 534 mysqlEP, err := mysql.Endpoint("server") 535 c.Assert(err, jc.ErrorIsNil) 536 relation, err := s.State.AddRelation(wordpressEP, mysqlEP) 537 c.Assert(err, jc.ErrorIsNil) 538 539 app, result, err := relation.RemoteApplication() 540 c.Assert(err, jc.ErrorIsNil) 541 c.Assert(result, jc.IsTrue) 542 c.Assert(app.Name(), gc.Equals, "remote-wordpress") 543 } 544 545 func (s *RelationSuite) TestAddCrossModelNotAllowed(c *gc.C) { 546 rwordpress, err := s.State.AddRemoteApplication(state.AddRemoteApplicationParams{ 547 Name: "remote-wordpress", 548 SourceModel: names.NewModelTag("source-model"), 549 OfferUUID: "offer-uuid", 550 Endpoints: []charm.Relation{{ 551 Interface: "mysql", 552 Limit: 1, 553 Name: "db", 554 Role: charm.RoleRequirer, 555 Scope: charm.ScopeGlobal, 556 }}, 557 }) 558 c.Assert(err, jc.ErrorIsNil) 559 wordpressEP, err := rwordpress.Endpoint("db") 560 c.Assert(err, jc.ErrorIsNil) 561 mysql := s.AddTestingApplication(c, "mysql", s.AddTestingCharm(c, "mysql")) 562 mysqlEP, err := mysql.Endpoint("server") 563 c.Assert(err, jc.ErrorIsNil) 564 565 err = rwordpress.SetStatus(status.StatusInfo{Status: status.Terminated}) 566 c.Assert(err, jc.ErrorIsNil) 567 _, err = s.State.AddRelation(wordpressEP, mysqlEP) 568 c.Assert(err, gc.ErrorMatches, `cannot add relation "remote-wordpress:db mysql:server": remote offer remote-wordpress is terminated`) 569 570 } 571 572 func (s *RelationSuite) TestIsCrossModelNope(c *gc.C) { 573 wordpress := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 574 wordpressEP, err := wordpress.Endpoint("db") 575 c.Assert(err, jc.ErrorIsNil) 576 mysql := s.AddTestingApplication(c, "mysql", s.AddTestingCharm(c, "mysql")) 577 mysqlEP, err := mysql.Endpoint("server") 578 c.Assert(err, jc.ErrorIsNil) 579 relation, err := s.State.AddRelation(wordpressEP, mysqlEP) 580 c.Assert(err, jc.ErrorIsNil) 581 582 app, result, err := relation.RemoteApplication() 583 c.Assert(err, jc.ErrorIsNil) 584 c.Assert(result, jc.IsFalse) 585 c.Assert(app, gc.IsNil) 586 } 587 588 func assertNoRelations(c *gc.C, app state.ApplicationEntity) { 589 rels, err := app.Relations() 590 c.Assert(err, jc.ErrorIsNil) 591 c.Assert(rels, gc.HasLen, 0) 592 } 593 594 func assertOneRelation(c *gc.C, srv *state.Application, relId int, endpoints ...state.Endpoint) *state.Relation { 595 rels, err := srv.Relations() 596 c.Assert(err, jc.ErrorIsNil) 597 c.Assert(rels, gc.HasLen, 1) 598 599 rel := rels[0] 600 c.Assert(rel.Id(), gc.Equals, relId) 601 602 c.Assert(rel.Endpoints(), jc.SameContents, endpoints) 603 604 name := srv.Name() 605 expectEp := endpoints[0] 606 ep, err := rel.Endpoint(name) 607 c.Assert(err, jc.ErrorIsNil) 608 c.Assert(ep, gc.DeepEquals, expectEp) 609 if len(endpoints) == 2 { 610 expectEp = endpoints[1] 611 } 612 eps, err := rel.RelatedEndpoints(name) 613 c.Assert(err, jc.ErrorIsNil) 614 c.Assert(eps, gc.DeepEquals, []state.Endpoint{expectEp}) 615 return rel 616 } 617 618 func (s *RelationSuite) TestRemoveAlsoDeletesNetworks(c *gc.C) { 619 wordpress := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 620 wordpressEP, err := wordpress.Endpoint("db") 621 c.Assert(err, jc.ErrorIsNil) 622 mysql := s.AddTestingApplication(c, "mysql", s.AddTestingCharm(c, "mysql")) 623 mysqlEP, err := mysql.Endpoint("server") 624 c.Assert(err, jc.ErrorIsNil) 625 relation, err := s.State.AddRelation(wordpressEP, mysqlEP) 626 c.Assert(err, jc.ErrorIsNil) 627 628 relIngress := state.NewRelationIngressNetworks(s.State) 629 _, err = relIngress.Save(relation.Tag().Id(), false, []string{"1.2.3.4/32", "4.3.2.1/16"}) 630 c.Assert(err, jc.ErrorIsNil) 631 _, err = relIngress.Save(relation.Tag().Id(), true, []string{"1.2.3.4/32", "4.3.2.1/16"}) 632 c.Assert(err, jc.ErrorIsNil) 633 634 relEgress := state.NewRelationEgressNetworks(s.State) 635 _, err = relEgress.Save(relation.Tag().Id(), false, []string{"1.2.3.4/32", "4.3.2.1/16"}) 636 c.Assert(err, jc.ErrorIsNil) 637 _, err = relEgress.Save(relation.Tag().Id(), true, []string{"1.2.3.4/32", "4.3.2.1/16"}) 638 c.Assert(err, jc.ErrorIsNil) 639 640 state.RemoveRelation(c, relation, false) 641 _, err = relIngress.Networks(relation.Tag().Id()) 642 c.Assert(err, jc.Satisfies, errors.IsNotFound) 643 _, err = relEgress.Networks(relation.Tag().Id()) 644 c.Assert(err, jc.Satisfies, errors.IsNotFound) 645 } 646 647 func (s *RelationSuite) TestRemoveAlsoDeletesRemoteTokens(c *gc.C) { 648 wordpress := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 649 wordpressEP, err := wordpress.Endpoint("db") 650 c.Assert(err, jc.ErrorIsNil) 651 mysql := s.AddTestingApplication(c, "mysql", s.AddTestingCharm(c, "mysql")) 652 mysqlEP, err := mysql.Endpoint("server") 653 c.Assert(err, jc.ErrorIsNil) 654 relation, err := s.State.AddRelation(wordpressEP, mysqlEP) 655 c.Assert(err, jc.ErrorIsNil) 656 657 // Add remote token so we can check it is cleaned up. 658 re := s.State.RemoteEntities() 659 relToken, err := re.ExportLocalEntity(relation.Tag()) 660 c.Assert(err, jc.ErrorIsNil) 661 662 state.RemoveRelation(c, relation, false) 663 _, err = re.GetToken(relation.Tag()) 664 c.Assert(err, jc.Satisfies, errors.IsNotFound) 665 _, err = re.GetRemoteEntity(relToken) 666 c.Assert(err, jc.Satisfies, errors.IsNotFound) 667 } 668 669 func (s *RelationSuite) TestRemoveAlsoDeletesRemoteOfferConnections(c *gc.C) { 670 rwordpress, err := s.State.AddRemoteApplication(state.AddRemoteApplicationParams{ 671 Name: "remote-wordpress", 672 SourceModel: names.NewModelTag("source-model"), 673 IsConsumerProxy: true, 674 OfferUUID: "offer-uuid", 675 Endpoints: []charm.Relation{{ 676 Interface: "mysql", 677 Limit: 1, 678 Name: "db", 679 Role: charm.RoleRequirer, 680 Scope: charm.ScopeGlobal, 681 }}, 682 }) 683 c.Assert(err, jc.ErrorIsNil) 684 wordpressEP, err := rwordpress.Endpoint("db") 685 c.Assert(err, jc.ErrorIsNil) 686 mysql := s.AddTestingApplication(c, "mysql", s.AddTestingCharm(c, "mysql")) 687 mysqlEP, err := mysql.Endpoint("server") 688 c.Assert(err, jc.ErrorIsNil) 689 relation, err := s.State.AddRelation(wordpressEP, mysqlEP) 690 c.Assert(err, jc.ErrorIsNil) 691 692 // Add a offer connection record so we can check it is cleaned up. 693 _, err = s.State.AddOfferConnection(state.AddOfferConnectionParams{ 694 SourceModelUUID: coretesting.ModelTag.Id(), 695 RelationId: relation.Id(), 696 RelationKey: relation.Tag().Id(), 697 Username: "fred", 698 OfferUUID: "offer-uuid", 699 }) 700 c.Assert(err, jc.ErrorIsNil) 701 rc, err := s.State.RemoteConnectionStatus("offer-uuid") 702 c.Assert(err, jc.ErrorIsNil) 703 c.Assert(rc.TotalConnectionCount(), gc.Equals, 1) 704 705 state.RemoveRelation(c, relation, false) 706 rc, err = s.State.RemoteConnectionStatus("offer-uuid") 707 c.Assert(err, jc.ErrorIsNil) 708 c.Assert(rc.TotalConnectionCount(), gc.Equals, 0) 709 } 710 711 func (s *RelationSuite) TestRemoveAlsoDeletesSecretPermissions(c *gc.C) { 712 relation := s.Factory.MakeRelation(c, nil) 713 app, err := s.State.Application(relation.Endpoints()[0].ApplicationName) 714 c.Assert(err, jc.ErrorIsNil) 715 store := state.NewSecrets(s.State) 716 uri := secrets.NewURI() 717 cp := state.CreateSecretParams{ 718 Version: 1, 719 Owner: app.Tag(), 720 UpdateSecretParams: state.UpdateSecretParams{ 721 LeaderToken: &fakeToken{}, 722 Data: map[string]string{"foo": "bar"}, 723 }, 724 } 725 _, err = store.CreateSecret(uri, cp) 726 c.Assert(err, jc.ErrorIsNil) 727 728 subject := names.NewApplicationTag("wordpress") 729 err = s.State.GrantSecretAccess(uri, state.SecretAccessParams{ 730 LeaderToken: &fakeToken{}, 731 Scope: relation.Tag(), 732 Subject: subject, 733 Role: secrets.RoleView, 734 }) 735 c.Assert(err, jc.ErrorIsNil) 736 access, err := s.State.SecretAccess(uri, subject) 737 c.Assert(err, jc.ErrorIsNil) 738 c.Assert(access, gc.Equals, secrets.RoleView) 739 740 state.RemoveRelation(c, relation, false) 741 access, err = s.State.SecretAccess(uri, subject) 742 c.Assert(err, jc.ErrorIsNil) 743 c.Assert(access, gc.Equals, secrets.RoleNone) 744 } 745 746 func (s *RelationSuite) TestRemoveNoFeatureFlag(c *gc.C) { 747 s.SetFeatureFlags( /*none*/ ) 748 wordpress := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 749 wordpressEP, err := wordpress.Endpoint("db") 750 c.Assert(err, jc.ErrorIsNil) 751 mysql := s.AddTestingApplication(c, "mysql", s.AddTestingCharm(c, "mysql")) 752 mysqlEP, err := mysql.Endpoint("server") 753 c.Assert(err, jc.ErrorIsNil) 754 relation, err := s.State.AddRelation(wordpressEP, mysqlEP) 755 c.Assert(err, jc.ErrorIsNil) 756 757 state.RemoveRelation(c, relation, false) 758 _, err = s.State.KeyRelation(relation.Tag().Id()) 759 c.Assert(err, jc.Satisfies, errors.IsNotFound) 760 } 761 762 func (s *RelationSuite) TestWatchLifeSuspendedStatus(c *gc.C) { 763 rel := s.setupRelationStatus(c) 764 mysql, err := s.State.Application("mysql") 765 c.Assert(err, jc.ErrorIsNil) 766 u, err := mysql.AddUnit(state.AddUnitParams{}) 767 c.Assert(err, jc.ErrorIsNil) 768 m := s.Factory.MakeMachine(c, &factory.MachineParams{}) 769 err = u.AssignToMachine(m) 770 c.Assert(err, jc.ErrorIsNil) 771 relUnit, err := rel.Unit(u) 772 c.Assert(err, jc.ErrorIsNil) 773 err = relUnit.EnterScope(nil) 774 c.Assert(err, jc.ErrorIsNil) 775 776 w := rel.WatchLifeSuspendedStatus() 777 defer testing.AssertStop(c, w) 778 wc := testing.NewStringsWatcherC(c, w) 779 // Initial event. 780 wc.AssertChange(rel.Tag().Id()) 781 wc.AssertNoChange() 782 783 err = rel.SetSuspended(true, "reason") 784 c.Assert(err, jc.ErrorIsNil) 785 wc.AssertChange(rel.Tag().Id()) 786 wc.AssertNoChange() 787 788 err = rel.Refresh() 789 c.Assert(err, jc.ErrorIsNil) 790 err = rel.Destroy() 791 c.Assert(err, jc.ErrorIsNil) 792 wc.AssertChange(rel.Tag().Id()) 793 wc.AssertNoChange() 794 } 795 796 func (s *RelationSuite) TestWatchLifeSuspendedStatusDead(c *gc.C) { 797 // Create a pair of application and a relation between them. 798 s.AddTestingApplication(c, "mysql", s.AddTestingCharm(c, "mysql")) 799 s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 800 eps, err := s.State.InferEndpoints("wordpress", "mysql") 801 c.Assert(err, jc.ErrorIsNil) 802 rel, err := s.State.AddRelation(eps...) 803 c.Assert(err, jc.ErrorIsNil) 804 805 w := rel.WatchLifeSuspendedStatus() 806 defer testing.AssertStop(c, w) 807 wc := testing.NewStringsWatcherC(c, w) 808 wc.AssertChange(rel.Tag().Id()) 809 810 err = rel.Destroy() 811 c.Assert(err, jc.ErrorIsNil) 812 wc.AssertChange(rel.Tag().Id()) 813 wc.AssertNoChange() 814 } 815 816 func (s *RelationSuite) setupRelationStatus(c *gc.C) *state.Relation { 817 wordpress := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 818 wordpressEP, err := wordpress.Endpoint("db") 819 c.Assert(err, jc.ErrorIsNil) 820 mysql := s.AddTestingApplication(c, "mysql", s.AddTestingCharm(c, "mysql")) 821 mysqlEP, err := mysql.Endpoint("server") 822 c.Assert(err, jc.ErrorIsNil) 823 rel, err := s.State.AddRelation(wordpressEP, mysqlEP) 824 c.Assert(err, jc.ErrorIsNil) 825 relStatus, err := rel.Status() 826 c.Assert(err, jc.ErrorIsNil) 827 c.Assert(relStatus.Status, gc.Equals, status.Joining) 828 ao := state.NewApplicationOffers(s.State) 829 offer, err := ao.AddOffer(crossmodel.AddApplicationOfferArgs{ 830 OfferName: "hosted-mysql", 831 ApplicationName: "mysql", 832 Owner: s.Owner.Id(), 833 }) 834 c.Assert(err, jc.ErrorIsNil) 835 user := s.Factory.MakeUser(c, &factory.UserParams{Name: "fred", Access: permission.WriteAccess}) 836 _, err = s.State.AddOfferConnection(state.AddOfferConnectionParams{ 837 SourceModelUUID: utils.MustNewUUID().String(), 838 OfferUUID: offer.OfferUUID, 839 RelationKey: rel.Tag().Id(), 840 RelationId: rel.Id(), 841 Username: user.Name(), 842 }) 843 c.Assert(err, jc.ErrorIsNil) 844 return rel 845 } 846 847 func (s *RelationSuite) TestStatus(c *gc.C) { 848 rel := s.setupRelationStatus(c) 849 err := rel.SetStatus(status.StatusInfo{ 850 Status: status.Suspended, 851 Message: "for a while", 852 }) 853 c.Assert(err, jc.ErrorIsNil) 854 relStatus, err := rel.Status() 855 c.Assert(err, jc.ErrorIsNil) 856 c.Assert(relStatus.Since, gc.NotNil) 857 relStatus.Since = nil 858 c.Assert(relStatus, jc.DeepEquals, status.StatusInfo{ 859 Status: status.Suspended, 860 Message: "for a while", 861 Data: map[string]interface{}{}, 862 }) 863 } 864 865 func (s *RelationSuite) TestInvalidStatus(c *gc.C) { 866 rel := s.setupRelationStatus(c) 867 868 err := rel.SetStatus(status.StatusInfo{ 869 Status: status.Status("invalid"), 870 }) 871 c.Assert(err, gc.ErrorMatches, `cannot set invalid status "invalid"`) 872 } 873 874 func (s *RelationSuite) TestSetSuspend(c *gc.C) { 875 rel := s.setupRelationStatus(c) 876 // Suspend doesn't need an offer connection to be there. 877 state.RemoveOfferConnectionsForRelation(c, rel) 878 c.Assert(rel.Suspended(), jc.IsFalse) 879 err := rel.SetSuspended(true, "reason") 880 c.Assert(err, jc.ErrorIsNil) 881 rel, err = s.State.Relation(rel.Id()) 882 c.Assert(err, jc.ErrorIsNil) 883 c.Assert(rel.Suspended(), jc.IsTrue) 884 c.Assert(rel.SuspendedReason(), gc.Equals, "reason") 885 } 886 887 func (s *RelationSuite) TestSetSuspendFalse(c *gc.C) { 888 rel := s.setupRelationStatus(c) 889 // Suspend doesn't need an offer connection to be there. 890 state.RemoveOfferConnectionsForRelation(c, rel) 891 c.Assert(rel.Suspended(), jc.IsFalse) 892 err := rel.SetSuspended(true, "reason") 893 c.Assert(err, jc.ErrorIsNil) 894 err = rel.SetSuspended(false, "reason") 895 c.Assert(err, gc.ErrorMatches, "cannot set suspended reason if not suspended") 896 err = rel.SetSuspended(false, "") 897 c.Assert(err, jc.ErrorIsNil) 898 rel, err = s.State.Relation(rel.Id()) 899 c.Assert(err, jc.ErrorIsNil) 900 c.Assert(rel.Suspended(), jc.IsFalse) 901 } 902 903 func (s *RelationSuite) TestApplicationSettings(c *gc.C) { 904 s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 905 s.AddTestingApplication(c, "mysql", s.AddTestingCharm(c, "mysql")) 906 eps, err := s.State.InferEndpoints("mysql", "wordpress") 907 c.Assert(err, jc.ErrorIsNil) 908 relation, err := s.State.AddRelation(eps...) 909 c.Assert(err, jc.ErrorIsNil) 910 911 settingsMap, err := relation.ApplicationSettings("mysql") 912 c.Assert(err, jc.ErrorIsNil) 913 c.Assert(settingsMap, gc.HasLen, 0) 914 915 settings := state.NewStateSettings(s.State) 916 key := fmt.Sprintf("r#%d#mysql", relation.Id()) 917 err = settings.ReplaceSettings(key, map[string]interface{}{ 918 "bailterspace": "blammo", 919 }) 920 c.Assert(err, jc.ErrorIsNil) 921 922 settingsMap, err = relation.ApplicationSettings("mysql") 923 c.Assert(err, jc.ErrorIsNil) 924 c.Assert(settingsMap, gc.DeepEquals, map[string]interface{}{ 925 "bailterspace": "blammo", 926 }) 927 } 928 929 func (s *RelationSuite) TestApplicationSettingsPeer(c *gc.C) { 930 app := state.AddTestingApplication(c, s.State, "riak", state.AddTestingCharm(c, s.State, "riak")) 931 ep, err := app.Endpoint("ring") 932 c.Assert(err, jc.ErrorIsNil) 933 rel, err := s.State.EndpointsRelation(ep) 934 c.Assert(err, jc.ErrorIsNil) 935 936 settings := state.NewStateSettings(s.State) 937 key := fmt.Sprintf("r#%d#riak", rel.Id()) 938 err = settings.ReplaceSettings(key, map[string]interface{}{ 939 "mermaidens": "disappear", 940 }) 941 c.Assert(err, jc.ErrorIsNil) 942 943 settingsMap, err := rel.ApplicationSettings("riak") 944 c.Assert(err, jc.ErrorIsNil) 945 c.Assert(settingsMap, gc.DeepEquals, map[string]interface{}{ 946 "mermaidens": "disappear", 947 }) 948 } 949 950 func (s *RelationSuite) TestApplicationSettingsErrors(c *gc.C) { 951 app := state.AddTestingApplication(c, s.State, "riak", state.AddTestingCharm(c, s.State, "riak")) 952 ep, err := app.Endpoint("ring") 953 c.Assert(err, jc.ErrorIsNil) 954 rel, err := s.State.EndpointsRelation(ep) 955 c.Assert(err, jc.ErrorIsNil) 956 957 state.AddTestingApplication(c, s.State, "wordpress", state.AddTestingCharm(c, s.State, "wordpress")) 958 959 settings, err := rel.ApplicationSettings("wordpress") 960 c.Assert(err, gc.ErrorMatches, `application "wordpress" is not a member of "riak:ring"`) 961 c.Assert(settings, gc.HasLen, 0) 962 } 963 964 func (s *RelationSuite) TestUpdateApplicationSettingsSuccess(c *gc.C) { 965 s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 966 s.AddTestingApplication(c, "mysql", s.AddTestingCharm(c, "mysql")) 967 eps, err := s.State.InferEndpoints("mysql", "wordpress") 968 c.Assert(err, jc.ErrorIsNil) 969 relation, err := s.State.AddRelation(eps...) 970 c.Assert(err, jc.ErrorIsNil) 971 972 // fakeToken always succeeds. 973 err = relation.UpdateApplicationSettings( 974 "mysql", &fakeToken{}, map[string]interface{}{ 975 "rendezvouse": "rendezvous", 976 "olden": "yolk", 977 }, 978 ) 979 c.Assert(err, jc.ErrorIsNil) 980 981 settingsMap, err := relation.ApplicationSettings("mysql") 982 c.Assert(err, jc.ErrorIsNil) 983 c.Assert(settingsMap, gc.DeepEquals, map[string]interface{}{ 984 "rendezvouse": "rendezvous", 985 "olden": "yolk", 986 }) 987 988 // Check that updates only overwrite existing keys. 989 err = relation.UpdateApplicationSettings( 990 "mysql", &fakeToken{}, map[string]interface{}{ 991 "olden": "times", 992 }, 993 ) 994 c.Assert(err, jc.ErrorIsNil) 995 settingsMap, err = relation.ApplicationSettings("mysql") 996 c.Assert(err, jc.ErrorIsNil) 997 c.Assert(settingsMap, gc.DeepEquals, map[string]interface{}{ 998 "rendezvouse": "rendezvous", 999 "olden": "times", 1000 }) 1001 } 1002 1003 func (s *RelationSuite) TestUpdateApplicationSettingsNotLeader(c *gc.C) { 1004 s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 1005 s.AddTestingApplication(c, "mysql", s.AddTestingCharm(c, "mysql")) 1006 eps, err := s.State.InferEndpoints("mysql", "wordpress") 1007 c.Assert(err, jc.ErrorIsNil) 1008 relation, err := s.State.AddRelation(eps...) 1009 c.Assert(err, jc.ErrorIsNil) 1010 1011 err = relation.UpdateApplicationSettings( 1012 "mysql", 1013 &fakeToken{errors.New("not the leader")}, 1014 map[string]interface{}{ 1015 "rendezvouse": "rendezvous", 1016 }, 1017 ) 1018 c.Assert(err, gc.ErrorMatches, 1019 `relation "wordpress:db mysql:server" application "mysql": checking leadership continuity: not the leader`) 1020 1021 settingsMap, err := relation.ApplicationSettings("mysql") 1022 c.Assert(err, jc.ErrorIsNil) 1023 c.Assert(settingsMap, gc.HasLen, 0) 1024 } 1025 1026 func (s *RelationSuite) TestWatchApplicationSettings(c *gc.C) { 1027 s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 1028 mysql := s.AddTestingApplication(c, "mysql", s.AddTestingCharm(c, "mysql")) 1029 eps, err := s.State.InferEndpoints("mysql", "wordpress") 1030 c.Assert(err, jc.ErrorIsNil) 1031 relation, err := s.State.AddRelation(eps...) 1032 c.Assert(err, jc.ErrorIsNil) 1033 s.WaitForModelWatchersIdle(c, s.Model.UUID()) 1034 1035 w, err := relation.WatchApplicationSettings(mysql) 1036 c.Assert(err, jc.ErrorIsNil) 1037 defer testing.AssertStop(c, w) 1038 1039 wc := testing.NewNotifyWatcherC(c, w) 1040 wc.AssertOneChange() 1041 1042 err = relation.UpdateApplicationSettings( 1043 "mysql", &fakeToken{}, map[string]interface{}{ 1044 "castor": "pollux", 1045 }, 1046 ) 1047 c.Assert(err, jc.ErrorIsNil) 1048 wc.AssertOneChange() 1049 1050 // No notify for a null change. 1051 err = relation.UpdateApplicationSettings( 1052 "mysql", &fakeToken{}, map[string]interface{}{ 1053 "castor": "pollux", 1054 }, 1055 ) 1056 c.Assert(err, jc.ErrorIsNil) 1057 wc.AssertNoChange() 1058 } 1059 1060 func (s *RelationSuite) TestWatchApplicationSettingsOtherEnd(c *gc.C) { 1061 s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 1062 mysql := s.AddTestingApplication(c, "mysql", s.AddTestingCharm(c, "mysql")) 1063 eps, err := s.State.InferEndpoints("mysql", "wordpress") 1064 c.Assert(err, jc.ErrorIsNil) 1065 relation, err := s.State.AddRelation(eps...) 1066 c.Assert(err, jc.ErrorIsNil) 1067 s.WaitForModelWatchersIdle(c, s.Model.UUID()) 1068 1069 w, err := relation.WatchApplicationSettings(mysql) 1070 c.Assert(err, jc.ErrorIsNil) 1071 defer testing.AssertStop(c, w) 1072 1073 wc := testing.NewNotifyWatcherC(c, w) 1074 wc.AssertOneChange() 1075 1076 // No notify if the other application's settings are changed. 1077 err = relation.UpdateApplicationSettings( 1078 "wordpress", &fakeToken{}, map[string]interface{}{ 1079 "grand": "palais", 1080 }, 1081 ) 1082 c.Assert(err, jc.ErrorIsNil) 1083 wc.AssertNoChange() 1084 } 1085 1086 func (s *RelationSuite) TestDestroyForceSchedulesCleanupForStuckUnits(c *gc.C) { 1087 wordpress := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 1088 mysql := s.AddTestingApplication(c, "mysql", s.AddTestingCharm(c, "mysql")) 1089 eps, err := s.State.InferEndpoints("wordpress", "mysql") 1090 c.Assert(err, jc.ErrorIsNil) 1091 rel, err := s.State.AddRelation(eps...) 1092 c.Assert(err, jc.ErrorIsNil) 1093 1094 // If a unit agent is gone or down for some reason, a unit might 1095 // not leave the relation scope when the relation goes to 1096 // dying. If the destroy is forced, we shouldn't wait indefinitely 1097 // for that unit to leave scope. 1098 addRelationUnit := func(c *gc.C, app *state.Application) *state.RelationUnit { 1099 unit, err := app.AddUnit(state.AddUnitParams{}) 1100 c.Assert(err, jc.ErrorIsNil) 1101 machine := s.Factory.MakeMachine(c, &factory.MachineParams{}) 1102 err = unit.AssignToMachine(machine) 1103 c.Assert(err, jc.ErrorIsNil) 1104 relUnit, err := rel.Unit(unit) 1105 c.Assert(err, jc.ErrorIsNil) 1106 err = relUnit.EnterScope(nil) 1107 c.Assert(err, jc.ErrorIsNil) 1108 return relUnit 1109 } 1110 1111 relUnits := []*state.RelationUnit{ 1112 addRelationUnit(c, wordpress), 1113 addRelationUnit(c, wordpress), 1114 addRelationUnit(c, mysql), 1115 } 1116 // Destroy one of the units to be sure the cleanup isn't 1117 // retrieving it. 1118 unit, err := s.State.Unit("mysql/0") 1119 c.Assert(err, jc.ErrorIsNil) 1120 err = unit.Destroy() 1121 c.Assert(err, jc.ErrorIsNil) 1122 err = unit.Refresh() 1123 c.Assert(err, jc.Satisfies, errors.IsNotFound) 1124 1125 s.assertRelationCleanedUp(c, rel, relUnits) 1126 } 1127 1128 func (s *RelationSuite) TestDestroyForceStuckSubordinateUnits(c *gc.C) { 1129 prr := newProReqRelation(c, &s.ConnSuite, charm.ScopeContainer) 1130 prr.allEnterScope(c) 1131 1132 rel := prr.rel 1133 relUnits := []*state.RelationUnit{ 1134 prr.pru0, prr.pru1, prr.rru0, prr.rru1, 1135 } 1136 s.assertRelationCleanedUp(c, rel, relUnits) 1137 } 1138 1139 func (s *RelationSuite) TestDestroyForceStuckRemoteUnits(c *gc.C) { 1140 mysqlEps := []charm.Relation{ 1141 { 1142 Interface: "mysql", 1143 Name: "db", 1144 Role: charm.RoleProvider, 1145 Scope: charm.ScopeGlobal, 1146 }, 1147 } 1148 _, err := s.State.AddRemoteApplication(state.AddRemoteApplicationParams{ 1149 Name: "mysql", 1150 SourceModel: s.Model.ModelTag(), 1151 Token: "t0", 1152 Endpoints: mysqlEps, 1153 }) 1154 c.Assert(err, jc.ErrorIsNil) 1155 1156 wordpress := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 1157 eps, err := s.State.InferEndpoints("wordpress", "mysql") 1158 c.Assert(err, jc.ErrorIsNil) 1159 rel, err := s.State.AddRelation(eps...) 1160 c.Assert(err, jc.ErrorIsNil) 1161 1162 unit, err := wordpress.AddUnit(state.AddUnitParams{}) 1163 c.Assert(err, jc.ErrorIsNil) 1164 machine := s.Factory.MakeMachine(c, &factory.MachineParams{}) 1165 err = unit.AssignToMachine(machine) 1166 c.Assert(err, jc.ErrorIsNil) 1167 localRelUnit, err := rel.Unit(unit) 1168 c.Assert(err, jc.ErrorIsNil) 1169 err = localRelUnit.EnterScope(nil) 1170 c.Assert(err, jc.ErrorIsNil) 1171 1172 remoteRelUnit, err := rel.RemoteUnit("mysql/0") 1173 c.Assert(err, jc.ErrorIsNil) 1174 err = remoteRelUnit.EnterScope(nil) 1175 c.Assert(err, jc.ErrorIsNil) 1176 1177 opErrs, err := rel.DestroyWithForce(true, time.Minute) 1178 c.Assert(opErrs, gc.HasLen, 0) 1179 c.Assert(err, jc.ErrorIsNil) 1180 1181 err = rel.Refresh() 1182 c.Assert(err, jc.ErrorIsNil) 1183 c.Assert(rel.Life(), gc.Equals, state.Dying) 1184 1185 // Schedules a cleanup to remove the unit scope if needed. 1186 s.assertNeedsCleanup(c) 1187 1188 // But running cleanup immediately doesn't do it all. 1189 err = s.State.Cleanup() 1190 c.Assert(err, jc.ErrorIsNil) 1191 s.assertNeedsCleanup(c) 1192 1193 // Remote units forced out of scope. 1194 assertInScope(c, localRelUnit) 1195 assertNotInScope(c, remoteRelUnit) 1196 1197 err = rel.Refresh() 1198 c.Assert(err, jc.ErrorIsNil) 1199 c.Assert(rel.Life(), gc.Equals, state.Dying) 1200 1201 s.Clock.Advance(time.Minute) 1202 1203 err = s.State.Cleanup() 1204 c.Assert(err, jc.ErrorIsNil) 1205 1206 assertNotInScope(c, localRelUnit) 1207 1208 err = rel.Refresh() 1209 c.Assert(err, jc.Satisfies, errors.IsNotFound) 1210 } 1211 1212 func (s *RelationSuite) TestDestroyForceIsFineIfUnitsAlreadyLeft(c *gc.C) { 1213 prr := newProReqRelation(c, &s.ConnSuite, charm.ScopeGlobal) 1214 prr.allEnterScope(c) 1215 rel := prr.rel 1216 relUnits := []*state.RelationUnit{ 1217 prr.pru0, prr.pru1, prr.rru0, prr.rru1, 1218 } 1219 opErrs, err := rel.DestroyWithForce(true, time.Minute) 1220 c.Assert(opErrs, gc.HasLen, 0) 1221 c.Assert(err, jc.ErrorIsNil) 1222 1223 err = rel.Refresh() 1224 c.Assert(err, jc.ErrorIsNil) 1225 c.Assert(rel.Life(), gc.Equals, state.Dying) 1226 1227 // Schedules a cleanup to remove the unit scope if needed. 1228 s.assertNeedsCleanup(c) 1229 1230 // But running cleanup immediately doesn't do it. 1231 err = s.State.Cleanup() 1232 c.Assert(err, jc.ErrorIsNil) 1233 s.assertNeedsCleanup(c) 1234 for i, ru := range relUnits { 1235 c.Logf("%d", i) 1236 assertJoined(c, ru) 1237 } 1238 err = rel.Refresh() 1239 c.Assert(err, jc.ErrorIsNil) 1240 c.Assert(rel.Life(), gc.Equals, state.Dying) 1241 1242 // In the meantime the units correctly leave scope. 1243 s.Clock.Advance(30 * time.Second) 1244 1245 for i, ru := range relUnits { 1246 c.Logf("%d", i) 1247 err := ru.LeaveScope() 1248 c.Assert(err, jc.ErrorIsNil) 1249 } 1250 err = rel.Refresh() 1251 c.Assert(err, jc.Satisfies, errors.IsNotFound) 1252 s.Clock.Advance(30 * time.Second) 1253 1254 err = s.State.Cleanup() 1255 c.Assert(err, jc.ErrorIsNil) 1256 1257 // If the cleanup had failed because the relation had gone, it 1258 // would be left in the collection. 1259 s.assertNoCleanups(c) 1260 } 1261 1262 func (s *RelationSuite) assertRelationCleanedUp(c *gc.C, rel *state.Relation, relUnits []*state.RelationUnit) { 1263 opErrs, err := rel.DestroyWithForce(true, time.Minute) 1264 c.Assert(opErrs, gc.HasLen, 0) 1265 c.Assert(err, jc.ErrorIsNil) 1266 1267 err = rel.Refresh() 1268 c.Assert(err, jc.ErrorIsNil) 1269 c.Assert(rel.Life(), gc.Equals, state.Dying) 1270 1271 // Schedules a cleanup to remove the unit scope if needed. 1272 s.assertNeedsCleanup(c) 1273 1274 // But running cleanup immediately doesn't do it. 1275 err = s.State.Cleanup() 1276 c.Assert(err, jc.ErrorIsNil) 1277 s.assertNeedsCleanup(c) 1278 for i, ru := range relUnits { 1279 c.Logf("%d", i) 1280 assertJoined(c, ru) 1281 } 1282 err = rel.Refresh() 1283 c.Assert(err, jc.ErrorIsNil) 1284 c.Assert(rel.Life(), gc.Equals, state.Dying) 1285 1286 s.Clock.Advance(time.Minute) 1287 1288 err = s.State.Cleanup() 1289 c.Assert(err, jc.ErrorIsNil) 1290 1291 for i, ru := range relUnits { 1292 c.Logf("%d", i) 1293 assertNotInScope(c, ru) 1294 } 1295 1296 err = rel.Refresh() 1297 c.Assert(err, jc.Satisfies, errors.IsNotFound) 1298 } 1299 1300 func (s *RelationSuite) assertNeedsCleanup(c *gc.C) { 1301 dirty, err := s.State.NeedsCleanup() 1302 c.Assert(err, jc.ErrorIsNil) 1303 c.Assert(dirty, jc.IsTrue) 1304 } 1305 1306 func (s *RelationSuite) assertNoCleanups(c *gc.C) { 1307 dirty, err := s.State.NeedsCleanup() 1308 c.Assert(err, jc.ErrorIsNil) 1309 c.Assert(dirty, jc.IsFalse) 1310 }