github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/apiserver/facades/agent/uniter/goal-state_test.go (about) 1 // Copyright 2018 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package uniter_test 5 6 import ( 7 "time" 8 9 "github.com/juju/errors" 10 jc "github.com/juju/testing/checkers" 11 gc "gopkg.in/check.v1" 12 "gopkg.in/juju/charm.v6" 13 14 "github.com/juju/juju/apiserver/common" 15 "github.com/juju/juju/apiserver/facade/facadetest" 16 "github.com/juju/juju/apiserver/facades/agent/uniter" 17 "github.com/juju/juju/apiserver/params" 18 apiservertesting "github.com/juju/juju/apiserver/testing" 19 "github.com/juju/juju/core/status" 20 "github.com/juju/juju/juju/testing" 21 "github.com/juju/juju/state" 22 coretesting "github.com/juju/juju/testing" 23 "github.com/juju/juju/testing/factory" 24 ) 25 26 // uniterSuite implements common testing suite for all API 27 // versions. It's not intended to be used directly or registered as a 28 // suite, but embedded. 29 type uniterGoalStateSuite struct { 30 testing.JujuConnSuite 31 32 authorizer apiservertesting.FakeAuthorizer 33 resources *common.Resources 34 uniter *uniter.UniterAPI 35 36 machine0 *state.Machine 37 machine1 *state.Machine 38 machine2 *state.Machine 39 logging *state.Application 40 wordpress *state.Application 41 wpCharm *state.Charm 42 mysql *state.Application 43 wordpressUnit *state.Unit 44 mysqlUnit *state.Unit 45 } 46 47 var _ = gc.Suite(&uniterGoalStateSuite{}) 48 49 func (s *uniterGoalStateSuite) SetUpTest(c *gc.C) { 50 s.JujuConnSuite.SetUpTest(c) 51 52 // Create two machines, two applications and add a unit to each application. 53 s.machine0 = s.Factory.MakeMachine(c, &factory.MachineParams{ 54 Series: "quantal", 55 Jobs: []state.MachineJob{state.JobHostUnits, state.JobManageModel}, 56 }) 57 s.machine1 = s.Factory.MakeMachine(c, &factory.MachineParams{ 58 Series: "quantal", 59 Jobs: []state.MachineJob{state.JobHostUnits}, 60 }) 61 s.machine2 = s.Factory.MakeMachine(c, &factory.MachineParams{ 62 Series: "quantal", 63 Jobs: []state.MachineJob{state.JobHostUnits}, 64 }) 65 66 mysqlCharm := s.Factory.MakeCharm(c, &factory.CharmParams{ 67 Name: "mysql", 68 }) 69 s.mysql = s.Factory.MakeApplication(c, &factory.ApplicationParams{ 70 Name: "mysql", 71 Charm: mysqlCharm, 72 }) 73 74 s.mysqlUnit = s.Factory.MakeUnit(c, &factory.UnitParams{ 75 Application: s.mysql, 76 Machine: s.machine1, 77 }) 78 79 s.wpCharm = s.Factory.MakeCharm(c, &factory.CharmParams{ 80 Name: "wordpress", 81 URL: "cs:quantal/wordpress-0", 82 }) 83 s.wordpress = s.Factory.MakeApplication(c, &factory.ApplicationParams{ 84 Name: "wordpress", 85 Charm: s.wpCharm, 86 }) 87 s.wordpressUnit = s.Factory.MakeUnit(c, &factory.UnitParams{ 88 Application: s.wordpress, 89 Machine: s.machine0, 90 }) 91 92 loggingCharm := s.Factory.MakeCharm(c, &factory.CharmParams{ 93 Name: "logging", 94 }) 95 s.logging = s.Factory.MakeApplication(c, &factory.ApplicationParams{ 96 Name: "logging", 97 Charm: loggingCharm, 98 }) 99 100 // Create a FakeAuthorizer so we can check permissions, 101 // set up assuming unit 0 has logged in. 102 s.authorizer = apiservertesting.FakeAuthorizer{ 103 Tag: s.mysql.Tag(), 104 } 105 106 // Create the resource registry separately to track invocations to 107 // Register. 108 s.resources = common.NewResources() 109 s.AddCleanup(func(_ *gc.C) { s.resources.StopAll() }) 110 111 uniterAPI, err := uniter.NewUniterAPI(facadetest.Context{ 112 State_: s.State, 113 Resources_: s.resources, 114 Auth_: s.authorizer, 115 LeadershipChecker_: s.State.LeadershipChecker(), 116 }) 117 c.Assert(err, jc.ErrorIsNil) 118 s.uniter = uniterAPI 119 } 120 121 var ( 122 timestamp = time.Date(2200, time.November, 5, 0, 0, 0, 0, time.UTC) 123 expectedUnitStatus = params.GoalStateStatus{ 124 Status: "waiting", 125 Since: ×tamp, 126 } 127 expectedRelationStatus = params.GoalStateStatus{ 128 Status: "joining", 129 Since: ×tamp, 130 } 131 expected2UnitsMysql = params.UnitsGoalState{ 132 "mysql/0": expectedUnitStatus, 133 "mysql/1": expectedUnitStatus, 134 } 135 expectedUnitMysql = params.UnitsGoalState{ 136 "mysql/0": expectedUnitStatus, 137 } 138 ) 139 140 // TestGoalStatesNoRelation tests single application with single unit. 141 func (s *uniterGoalStateSuite) TestGoalStatesNoRelation(c *gc.C) { 142 args := params.Entities{Entities: []params.Entity{ 143 {Tag: "unit-mysql-0"}, 144 {Tag: "unit-wordpress-0"}, 145 }} 146 expected := params.GoalStateResults{ 147 Results: []params.GoalStateResult{ 148 { 149 Result: ¶ms.GoalState{ 150 Units: expectedUnitMysql, 151 }, 152 }, { 153 Error: apiservertesting.ErrUnauthorized, 154 }, 155 }, 156 } 157 testGoalStates(c, s.uniter, args, expected) 158 } 159 160 // TestGoalStatesNoRelationTwoUnits adds a new unit to wordpress application. 161 func (s *uniterGoalStateSuite) TestPeerUnitsNoRelation(c *gc.C) { 162 args := params.Entities{Entities: []params.Entity{ 163 {Tag: "unit-mysql-0"}, 164 {Tag: "unit-wordpress-0"}, 165 }} 166 167 s.Factory.MakeUnit(c, &factory.UnitParams{ 168 Application: s.mysql, 169 Machine: s.machine1, 170 }) 171 172 expected := params.GoalStateResults{ 173 Results: []params.GoalStateResult{ 174 { 175 Result: ¶ms.GoalState{ 176 Units: expected2UnitsMysql, 177 }, 178 }, { 179 Error: apiservertesting.ErrUnauthorized, 180 }, 181 }, 182 } 183 testGoalStates(c, s.uniter, args, expected) 184 } 185 186 // TestGoalStatesSingleRelation tests structure with two different 187 // application units and one relation between the units. 188 func (s *uniterGoalStateSuite) TestGoalStatesSingleRelation(c *gc.C) { 189 190 err := s.addRelationEnterScope(c, s.mysqlUnit, "wordpress") 191 c.Assert(err, jc.ErrorIsNil) 192 193 args := params.Entities{Entities: []params.Entity{ 194 {Tag: "unit-mysql-0"}, 195 {Tag: "unit-wordpress-0"}, 196 }} 197 expected := params.GoalStateResults{ 198 Results: []params.GoalStateResult{ 199 { 200 Result: ¶ms.GoalState{ 201 Units: expectedUnitMysql, 202 Relations: map[string]params.UnitsGoalState{ 203 "db": { 204 "wordpress": expectedRelationStatus, 205 "wordpress/0": expectedUnitStatus, 206 }, 207 }, 208 }, 209 }, { 210 Error: apiservertesting.ErrUnauthorized, 211 }, 212 }, 213 } 214 testGoalStates(c, s.uniter, args, expected) 215 } 216 217 // TestGoalStatesDeadUnitsExcluded tests dead units should not show in the GoalState result. 218 func (s *uniterGoalStateSuite) TestGoalStatesDeadUnitsExcluded(c *gc.C) { 219 220 err := s.addRelationEnterScope(c, s.wordpressUnit, "mysql") 221 c.Assert(err, jc.ErrorIsNil) 222 223 newMysqlUnit := s.Factory.MakeUnit(c, &factory.UnitParams{ 224 Application: s.mysql, 225 Machine: s.machine1, 226 }) 227 args := params.Entities{Entities: []params.Entity{ 228 {Tag: "unit-mysql-0"}, 229 }} 230 testGoalStates(c, s.uniter, args, params.GoalStateResults{ 231 Results: []params.GoalStateResult{ 232 { 233 Result: ¶ms.GoalState{ 234 Units: expected2UnitsMysql, 235 Relations: map[string]params.UnitsGoalState{ 236 "db": { 237 "wordpress": expectedRelationStatus, 238 "wordpress/0": expectedUnitStatus, 239 }, 240 }, 241 }, 242 }, 243 }, 244 }) 245 246 err = newMysqlUnit.Destroy() 247 c.Assert(err, jc.ErrorIsNil) 248 249 testGoalStates(c, s.uniter, args, params.GoalStateResults{ 250 Results: []params.GoalStateResult{ 251 { 252 Result: ¶ms.GoalState{ 253 Units: params.UnitsGoalState{ 254 "mysql/0": expectedUnitStatus, 255 }, 256 Relations: map[string]params.UnitsGoalState{ 257 "db": { 258 "wordpress": expectedRelationStatus, 259 "wordpress/0": expectedUnitStatus, 260 }, 261 }, 262 }, 263 }, 264 }, 265 }) 266 } 267 268 // preventUnitDestroyRemove sets a non-allocating status on the unit, and hence 269 // prevents it from being unceremoniously removed from state on Destroy. This 270 // is useful because several tests go through a unit's lifecycle step by step, 271 // asserting the behaviour of a given method in each state, and the unit quick- 272 // remove change caused many of these to fail. 273 func preventUnitDestroyRemove(c *gc.C, u *state.Unit) { 274 // To have a non-allocating status, a unit needs to 275 // be assigned to a machine. 276 _, err := u.AssignedMachineId() 277 if errors.IsNotAssigned(err) { 278 err = u.AssignToNewMachine() 279 } 280 c.Assert(err, jc.ErrorIsNil) 281 now := time.Now() 282 sInfo := status.StatusInfo{ 283 Status: status.Idle, 284 Message: "", 285 Since: &now, 286 } 287 err = u.SetAgentStatus(sInfo) 288 c.Assert(err, jc.ErrorIsNil) 289 } 290 291 // TestGoalStatesSingleRelationDyingUnits tests dying units showing dying status in the GoalState result. 292 func (s *uniterGoalStateSuite) TestGoalStatesSingleRelationDyingUnits(c *gc.C) { 293 mysqlUnit := s.Factory.MakeUnit(c, &factory.UnitParams{ 294 Application: s.mysql, 295 Machine: s.machine1, 296 }) 297 298 err := s.addRelationEnterScope(c, mysqlUnit, "wordpress") 299 c.Assert(err, jc.ErrorIsNil) 300 301 args := params.Entities{Entities: []params.Entity{ 302 {Tag: "unit-mysql-0"}, 303 }} 304 testGoalStates(c, s.uniter, args, params.GoalStateResults{ 305 Results: []params.GoalStateResult{ 306 { 307 Result: ¶ms.GoalState{ 308 Units: expected2UnitsMysql, 309 Relations: map[string]params.UnitsGoalState{ 310 "db": { 311 "wordpress": expectedRelationStatus, 312 "wordpress/0": expectedUnitStatus, 313 }, 314 }, 315 }, 316 }, 317 }, 318 }) 319 preventUnitDestroyRemove(c, mysqlUnit) 320 err = mysqlUnit.Destroy() 321 c.Assert(err, jc.ErrorIsNil) 322 323 testGoalStates(c, s.uniter, args, params.GoalStateResults{ 324 Results: []params.GoalStateResult{ 325 { 326 Result: ¶ms.GoalState{ 327 Units: params.UnitsGoalState{ 328 "mysql/0": expectedUnitStatus, 329 "mysql/1": params.GoalStateStatus{ 330 Status: "dying", 331 Since: ×tamp, 332 }, 333 }, 334 Relations: map[string]params.UnitsGoalState{ 335 "db": { 336 "wordpress": expectedRelationStatus, 337 "wordpress/0": expectedUnitStatus, 338 }, 339 }, 340 }, 341 }, 342 }, 343 }) 344 } 345 346 // TestGoalStatesCrossModelRelation tests remote relation application shows URL as key. 347 func (s *uniterGoalStateSuite) TestGoalStatesCrossModelRelation(c *gc.C) { 348 err := s.addRelationEnterScope(c, s.mysqlUnit, "wordpress") 349 c.Assert(err, jc.ErrorIsNil) 350 351 args := params.Entities{Entities: []params.Entity{ 352 {Tag: "unit-mysql-0"}, 353 }} 354 testGoalStates(c, s.uniter, args, params.GoalStateResults{Results: []params.GoalStateResult{ 355 { 356 Result: ¶ms.GoalState{ 357 Units: params.UnitsGoalState{ 358 "mysql/0": expectedUnitStatus, 359 }, 360 Relations: map[string]params.UnitsGoalState{ 361 "db": { 362 "wordpress": expectedRelationStatus, 363 "wordpress/0": expectedUnitStatus, 364 }, 365 }, 366 }, 367 }, 368 }}) 369 _, err = s.State.AddRemoteApplication(state.AddRemoteApplicationParams{ 370 Name: "metrics-remote", 371 URL: "ctrl1:admin/default.metrics", 372 SourceModel: coretesting.ModelTag, 373 Endpoints: []charm.Relation{{ 374 Interface: "metrics", 375 Name: "metrics", 376 Role: charm.RoleProvider, 377 Scope: charm.ScopeGlobal, 378 }}, 379 }) 380 c.Assert(err, jc.ErrorIsNil) 381 eps, err := s.State.InferEndpoints("mysql", "metrics-remote") 382 c.Assert(err, jc.ErrorIsNil) 383 _, err = s.State.AddRelation(eps...) 384 c.Assert(err, jc.ErrorIsNil) 385 testGoalStates(c, s.uniter, args, params.GoalStateResults{Results: []params.GoalStateResult{ 386 { 387 Result: ¶ms.GoalState{ 388 Units: params.UnitsGoalState{ 389 "mysql/0": expectedUnitStatus, 390 }, 391 Relations: map[string]params.UnitsGoalState{ 392 "db": { 393 "wordpress": expectedRelationStatus, 394 "wordpress/0": expectedUnitStatus, 395 }, 396 "metrics": { 397 "ctrl1:admin/default.metrics": expectedRelationStatus, 398 }, 399 }, 400 }, 401 }, 402 }}) 403 } 404 405 // TestGoalStatesMultipleRelations tests GoalStates with three 406 // applications one application has two units and each unit is related 407 // to a different application unit. 408 func (s *uniterGoalStateSuite) TestGoalStatesMultipleRelations(c *gc.C) { 409 410 // Add another wordpress unit on machine 1. 411 s.Factory.MakeUnit(c, &factory.UnitParams{ 412 Application: s.wordpress, 413 Machine: s.machine1, 414 }) 415 416 mysqlCharm1 := s.Factory.MakeCharm(c, &factory.CharmParams{ 417 Name: "mysql", 418 }) 419 mysql1 := s.Factory.MakeApplication(c, &factory.ApplicationParams{ 420 Name: "mysql1", 421 Charm: mysqlCharm1, 422 }) 423 424 mysqlUnit1 := s.Factory.MakeUnit(c, &factory.UnitParams{ 425 Application: mysql1, 426 Machine: s.machine2, 427 }) 428 429 err := s.addRelationEnterScope(c, s.wordpressUnit, "mysql") 430 c.Assert(err, jc.ErrorIsNil) 431 432 err = s.addRelationEnterScope(c, s.mysqlUnit, "logging") 433 c.Assert(err, jc.ErrorIsNil) 434 err = s.addRelationEnterScope(c, mysqlUnit1, "logging") 435 c.Assert(err, jc.ErrorIsNil) 436 437 args := params.Entities{Entities: []params.Entity{ 438 {Tag: "unit-mysql-0"}, 439 {Tag: "unit-wordpress-0"}, 440 }} 441 442 expected := params.GoalStateResults{ 443 Results: []params.GoalStateResult{ 444 { 445 Result: ¶ms.GoalState{ 446 Units: expectedUnitMysql, 447 Relations: map[string]params.UnitsGoalState{ 448 "db": { 449 "wordpress": expectedRelationStatus, 450 "wordpress/0": expectedUnitStatus, 451 "wordpress/1": expectedUnitStatus, 452 }, 453 "info": { 454 "logging": expectedRelationStatus, 455 "logging/0": expectedUnitStatus, 456 }, 457 }, 458 }, 459 }, { 460 Error: apiservertesting.ErrUnauthorized, 461 }, 462 }, 463 } 464 465 testGoalStates(c, s.uniter, args, expected) 466 } 467 468 func (s *uniterGoalStateSuite) addRelationEnterScope(c *gc.C, unit1 *state.Unit, app2 string) error { 469 app1, err := unit1.Application() 470 c.Assert(err, jc.ErrorIsNil) 471 472 relation := s.addRelation(c, app1.Name(), app2) 473 relationUnit, err := relation.Unit(unit1) 474 c.Assert(err, jc.ErrorIsNil) 475 476 err = relationUnit.EnterScope(nil) 477 c.Assert(err, jc.ErrorIsNil) 478 s.assertInScope(c, relationUnit, true) 479 return err 480 } 481 482 func (s *uniterGoalStateSuite) addRelation(c *gc.C, first, second string) *state.Relation { 483 eps, err := s.State.InferEndpoints(first, second) 484 c.Assert(err, jc.ErrorIsNil) 485 rel, err := s.State.AddRelation(eps...) 486 c.Assert(err, jc.ErrorIsNil) 487 return rel 488 } 489 490 func (s *uniterGoalStateSuite) assertInScope(c *gc.C, relUnit *state.RelationUnit, inScope bool) { 491 ok, err := relUnit.InScope() 492 c.Assert(err, jc.ErrorIsNil) 493 c.Assert(ok, gc.Equals, inScope) 494 } 495 496 // goalStates call uniter.GoalStates API and compares the output with the 497 // expected result. 498 func testGoalStates(c *gc.C, thisUniter *uniter.UniterAPI, args params.Entities, expected params.GoalStateResults) { 499 result, err := thisUniter.GoalStates(args) 500 c.Assert(err, jc.ErrorIsNil) 501 for i := range result.Results { 502 if result.Results[i].Error != nil { 503 break 504 } 505 setSinceToNil(c, result.Results[i].Result) 506 } 507 c.Assert(err, jc.ErrorIsNil) 508 c.Assert(result, jc.DeepEquals, expected) 509 } 510 511 // setSinceToNil will set the field `since` to nil in order to 512 // avoid a time check which otherwise would be impossible to pass. 513 func setSinceToNil(c *gc.C, goalState *params.GoalState) { 514 515 for i, u := range goalState.Units { 516 c.Assert(u.Since, gc.NotNil) 517 u.Since = ×tamp 518 goalState.Units[i] = u 519 } 520 for endPoint, gs := range goalState.Relations { 521 for key, m := range gs { 522 c.Assert(m.Since, gc.NotNil) 523 m.Since = ×tamp 524 gs[key] = m 525 } 526 goalState.Relations[endPoint] = gs 527 } 528 }