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  }