github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/worker/uniter/relation/hookqueue_test.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package relation_test 5 6 import ( 7 stdtesting "testing" 8 "time" 9 10 gc "launchpad.net/gocheck" 11 12 "github.com/juju/juju/charm/hooks" 13 "github.com/juju/juju/state/api/params" 14 coretesting "github.com/juju/juju/testing" 15 "github.com/juju/juju/worker/uniter/hook" 16 "github.com/juju/juju/worker/uniter/relation" 17 ) 18 19 func Test(t *stdtesting.T) { coretesting.MgoTestPackage(t) } 20 21 type HookQueueSuite struct{} 22 23 var _ = gc.Suite(&HookQueueSuite{}) 24 25 type msi map[string]int64 26 27 type hookQueueTest struct { 28 summary string 29 initial *relation.State 30 steps []checker 31 } 32 33 func fullTest(summary string, steps ...checker) hookQueueTest { 34 return hookQueueTest{summary, &relation.State{21345, nil, ""}, steps} 35 } 36 37 func reconcileTest(summary string, members msi, joined string, steps ...checker) hookQueueTest { 38 return hookQueueTest{summary, &relation.State{21345, members, joined}, steps} 39 } 40 41 var aliveHookQueueTests = []hookQueueTest{ 42 fullTest( 43 "Empty initial change causes no hooks.", 44 send{nil, nil}, 45 ), fullTest( 46 "Joined and changed are both run when unit is first detected.", 47 send{msi{"u/0": 0}, nil}, 48 expect{hooks.RelationJoined, "u/0", 0}, 49 expect{hooks.RelationChanged, "u/0", 0}, 50 ), fullTest( 51 "Automatic changed is run with latest settings.", 52 send{msi{"u/0": 0}, nil}, 53 expect{hooks.RelationJoined, "u/0", 0}, 54 send{msi{"u/0": 7}, nil}, 55 expect{hooks.RelationChanged, "u/0", 7}, 56 ), fullTest( 57 "Joined is also run with latest settings.", 58 send{msi{"u/0": 0}, nil}, 59 send{msi{"u/0": 7}, nil}, 60 expect{hooks.RelationJoined, "u/0", 7}, 61 expect{hooks.RelationChanged, "u/0", 7}, 62 ), fullTest( 63 "Nothing happens if a unit departs before its joined is run.", 64 send{msi{"u/0": 0}, nil}, 65 send{msi{"u/0": 7}, nil}, 66 send{nil, []string{"u/0"}}, 67 ), fullTest( 68 "A changed is run after a joined, even if a departed is known.", 69 send{msi{"u/0": 0}, nil}, 70 expect{hooks.RelationJoined, "u/0", 0}, 71 send{nil, []string{"u/0"}}, 72 expect{hooks.RelationChanged, "u/0", 0}, 73 expect{hooks.RelationDeparted, "u/0", 0}, 74 ), fullTest( 75 "A departed replaces a changed.", 76 send{msi{"u/0": 0}, nil}, 77 advance{2}, 78 send{msi{"u/0": 7}, nil}, 79 send{nil, []string{"u/0"}}, 80 expect{hooks.RelationDeparted, "u/0", 7}, 81 ), fullTest( 82 "Changed events are ignored if the version has not changed.", 83 send{msi{"u/0": 0}, nil}, 84 advance{2}, 85 send{msi{"u/0": 0}, nil}, 86 ), fullTest( 87 "Redundant changed events are elided.", 88 send{msi{"u/0": 0}, nil}, 89 advance{2}, 90 send{msi{"u/0": 3}, nil}, 91 send{msi{"u/0": 7}, nil}, 92 send{msi{"u/0": 79}, nil}, 93 expect{hooks.RelationChanged, "u/0", 79}, 94 ), fullTest( 95 "Latest hooks are run in the original unit order.", 96 send{msi{"u/0": 0, "u/1": 1}, nil}, 97 advance{4}, 98 send{msi{"u/0": 3}, nil}, 99 send{msi{"u/1": 7}, nil}, 100 send{nil, []string{"u/0"}}, 101 expect{hooks.RelationDeparted, "u/0", 3}, 102 expect{hooks.RelationChanged, "u/1", 7}, 103 ), fullTest( 104 "Test everything we can think of at the same time.", 105 send{msi{"u/0": 0, "u/1": 0, "u/2": 0, "u/3": 0, "u/4": 0}, nil}, 106 advance{6}, 107 // u/0, u/1, u/2 are now up to date; u/3, u/4 are untouched. 108 send{msi{"u/0": 1}, nil}, 109 send{msi{"u/1": 1, "u/2": 1, "u/3": 1, "u/5": 0}, []string{"u/0", "u/4"}}, 110 send{msi{"u/3": 2}, nil}, 111 // - Finish off the rest of the initial state, ignoring u/4, but using 112 // the latest known settings. 113 expect{hooks.RelationJoined, "u/3", 2}, 114 expect{hooks.RelationChanged, "u/3", 2}, 115 // - u/0 was queued for change by the first RUC, but this change is 116 // no longer relevant; it's departed in the second RUC, so we run 117 // that hook instead. 118 expect{hooks.RelationDeparted, "u/0", 1}, 119 // - Handle the remaining changes in the second RUC, still ignoring u/4. 120 // We do run new changed hooks for u/1 and u/2, because the latest settings 121 // are newer than those used in their original changed events. 122 expect{hooks.RelationChanged, "u/1", 1}, 123 expect{hooks.RelationChanged, "u/2", 1}, 124 expect{hooks.RelationJoined, "u/5", 0}, 125 expect{hooks.RelationChanged, "u/5", 0}, 126 // - Ignore the third RUC, because the original joined/changed on u/3 127 // was executed after we got the latest settings version. 128 ), reconcileTest( 129 "Check that matching settings versions cause no changes.", 130 msi{"u/0": 0}, "", 131 send{msi{"u/0": 0}, nil}, 132 ), reconcileTest( 133 "Check that new settings versions cause appropriate changes.", 134 msi{"u/0": 0}, "", 135 send{msi{"u/0": 1}, nil}, 136 expect{hooks.RelationChanged, "u/0", 1}, 137 ), reconcileTest( 138 "Check that a just-joined unit gets its changed hook run first.", 139 msi{"u/0": 0}, "u/0", 140 send{msi{"u/0": 0}, nil}, 141 expect{hooks.RelationChanged, "u/0", 0}, 142 ), reconcileTest( 143 "Check that missing units are queued for depart as early as possible.", 144 msi{"u/0": 0}, "", 145 send{msi{"u/1": 0}, nil}, 146 expect{hooks.RelationDeparted, "u/0", 0}, 147 expect{hooks.RelationJoined, "u/1", 0}, 148 expect{hooks.RelationChanged, "u/1", 0}, 149 ), reconcileTest( 150 "Double-check that a pending changed happens before an injected departed.", 151 msi{"u/0": 0}, "u/0", 152 send{nil, nil}, 153 expect{hooks.RelationChanged, "u/0", 0}, 154 expect{hooks.RelationDeparted, "u/0", 0}, 155 ), reconcileTest( 156 "Check that missing units don't slip in front of required changed hooks.", 157 msi{"u/0": 0}, "u/0", 158 send{msi{"u/1": 0}, nil}, 159 expect{hooks.RelationChanged, "u/0", 0}, 160 expect{hooks.RelationDeparted, "u/0", 0}, 161 expect{hooks.RelationJoined, "u/1", 0}, 162 expect{hooks.RelationChanged, "u/1", 0}, 163 ), 164 } 165 166 func (s *HookQueueSuite) TestAliveHookQueue(c *gc.C) { 167 for i, t := range aliveHookQueueTests { 168 c.Logf("test %d: %s", i, t.summary) 169 out := make(chan hook.Info) 170 in := make(chan params.RelationUnitsChange) 171 ruw := &RUW{in, false} 172 q := relation.NewAliveHookQueue(t.initial, out, ruw) 173 for i, step := range t.steps { 174 c.Logf(" step %d", i) 175 step.check(c, in, out) 176 } 177 expect{}.check(c, in, out) 178 q.Stop() 179 c.Assert(ruw.stopped, gc.Equals, true) 180 } 181 } 182 183 var dyingHookQueueTests = []hookQueueTest{ 184 fullTest( 185 "Empty state just gets a broken hook.", 186 expect{hook: hooks.RelationBroken}, 187 ), reconcileTest( 188 "Each current member is departed before broken is sent.", 189 msi{"u/1": 7, "u/4": 33}, "", 190 expect{hooks.RelationDeparted, "u/1", 7}, 191 expect{hooks.RelationDeparted, "u/4", 33}, 192 expect{hook: hooks.RelationBroken}, 193 ), reconcileTest( 194 "If there's a pending changed, that must still be respected.", 195 msi{"u/0": 3}, "u/0", 196 expect{hooks.RelationChanged, "u/0", 3}, 197 expect{hooks.RelationDeparted, "u/0", 3}, 198 expect{hook: hooks.RelationBroken}, 199 ), 200 } 201 202 func (s *HookQueueSuite) TestDyingHookQueue(c *gc.C) { 203 for i, t := range dyingHookQueueTests { 204 c.Logf("test %d: %s", i, t.summary) 205 out := make(chan hook.Info) 206 q := relation.NewDyingHookQueue(t.initial, out) 207 for i, step := range t.steps { 208 c.Logf(" step %d", i) 209 step.check(c, nil, out) 210 } 211 expect{}.check(c, nil, out) 212 q.Stop() 213 } 214 } 215 216 // RUW exists entirely to send RelationUnitsChanged events to a tested 217 // HookQueue in a synchronous and predictable fashion. 218 type RUW struct { 219 in chan params.RelationUnitsChange 220 stopped bool 221 } 222 223 func (w *RUW) Changes() <-chan params.RelationUnitsChange { 224 return w.in 225 } 226 227 func (w *RUW) Stop() error { 228 close(w.in) 229 w.stopped = true 230 return nil 231 } 232 233 func (w *RUW) Err() error { 234 return nil 235 } 236 237 type checker interface { 238 check(c *gc.C, in chan params.RelationUnitsChange, out chan hook.Info) 239 } 240 241 type send struct { 242 changed msi 243 departed []string 244 } 245 246 func (d send) check(c *gc.C, in chan params.RelationUnitsChange, out chan hook.Info) { 247 ruc := params.RelationUnitsChange{Changed: map[string]params.UnitSettings{}} 248 for name, version := range d.changed { 249 ruc.Changed[name] = params.UnitSettings{Version: version} 250 } 251 for _, name := range d.departed { 252 ruc.Departed = append(ruc.Departed, name) 253 } 254 in <- ruc 255 } 256 257 type advance struct { 258 count int 259 } 260 261 func (d advance) check(c *gc.C, in chan params.RelationUnitsChange, out chan hook.Info) { 262 for i := 0; i < d.count; i++ { 263 select { 264 case <-out: 265 case <-time.After(coretesting.LongWait): 266 c.Fatalf("timed out waiting for event %d", i) 267 } 268 } 269 } 270 271 type expect struct { 272 hook hooks.Kind 273 unit string 274 version int64 275 } 276 277 func (d expect) check(c *gc.C, in chan params.RelationUnitsChange, out chan hook.Info) { 278 if d.hook == "" { 279 select { 280 case unexpected := <-out: 281 c.Fatalf("got %#v", unexpected) 282 case <-time.After(coretesting.ShortWait): 283 } 284 return 285 } 286 expect := hook.Info{ 287 Kind: d.hook, 288 RelationId: 21345, 289 RemoteUnit: d.unit, 290 ChangeVersion: d.version, 291 } 292 select { 293 case actual := <-out: 294 c.Assert(actual, gc.DeepEquals, expect) 295 case <-time.After(coretesting.LongWait): 296 c.Fatalf("timed out waiting for %#v", expect) 297 } 298 }