github.com/rogpeppe/juju@v0.0.0-20140613142852-6337964b789e/worker/uniter/filter_test.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package uniter
     5  
     6  import (
     7  	"fmt"
     8  	"time"
     9  
    10  	"github.com/juju/charm"
    11  	"github.com/juju/utils"
    12  	gc "launchpad.net/gocheck"
    13  	"launchpad.net/tomb"
    14  
    15  	jujutesting "github.com/juju/juju/juju/testing"
    16  	"github.com/juju/juju/state"
    17  	"github.com/juju/juju/state/api"
    18  	"github.com/juju/juju/state/api/params"
    19  	apiuniter "github.com/juju/juju/state/api/uniter"
    20  	statetesting "github.com/juju/juju/state/testing"
    21  	coretesting "github.com/juju/juju/testing"
    22  	"github.com/juju/juju/worker"
    23  )
    24  
    25  type FilterSuite struct {
    26  	jujutesting.JujuConnSuite
    27  	wordpress  *state.Service
    28  	unit       *state.Unit
    29  	mysqlcharm *state.Charm
    30  	wpcharm    *state.Charm
    31  
    32  	st     *api.State
    33  	uniter *apiuniter.State
    34  }
    35  
    36  var _ = gc.Suite(&FilterSuite{})
    37  
    38  func (s *FilterSuite) SetUpTest(c *gc.C) {
    39  	s.JujuConnSuite.SetUpTest(c)
    40  	s.wpcharm = s.AddTestingCharm(c, "wordpress")
    41  	s.wordpress = s.AddTestingService(c, "wordpress", s.wpcharm)
    42  	var err error
    43  	s.unit, err = s.wordpress.AddUnit()
    44  	c.Assert(err, gc.IsNil)
    45  	err = s.unit.AssignToNewMachine()
    46  	c.Assert(err, gc.IsNil)
    47  	mid, err := s.unit.AssignedMachineId()
    48  	c.Assert(err, gc.IsNil)
    49  	machine, err := s.State.Machine(mid)
    50  	c.Assert(err, gc.IsNil)
    51  	err = machine.SetProvisioned("i-exist", "fake_nonce", nil)
    52  	c.Assert(err, gc.IsNil)
    53  	s.APILogin(c, s.unit)
    54  }
    55  
    56  func (s *FilterSuite) APILogin(c *gc.C, unit *state.Unit) {
    57  	password, err := utils.RandomPassword()
    58  	c.Assert(err, gc.IsNil)
    59  	err = unit.SetPassword(password)
    60  	c.Assert(err, gc.IsNil)
    61  	s.st = s.OpenAPIAs(c, unit.Tag(), password)
    62  	s.uniter = s.st.Uniter()
    63  	c.Assert(s.uniter, gc.NotNil)
    64  }
    65  
    66  func (s *FilterSuite) TestUnitDeath(c *gc.C) {
    67  	f, err := newFilter(s.uniter, s.unit.Tag())
    68  	c.Assert(err, gc.IsNil)
    69  	defer f.Stop() // no AssertStop, we test for an error below
    70  	asserter := coretesting.NotifyAsserterC{
    71  		Precond: func() { s.BackingState.StartSync() },
    72  		C:       c,
    73  		Chan:    f.UnitDying(),
    74  	}
    75  	asserter.AssertNoReceive()
    76  
    77  	// Irrelevant change.
    78  	err = s.unit.SetResolved(state.ResolvedRetryHooks)
    79  	c.Assert(err, gc.IsNil)
    80  	asserter.AssertNoReceive()
    81  
    82  	// Set dying.
    83  	err = s.unit.SetStatus(params.StatusStarted, "", nil)
    84  	c.Assert(err, gc.IsNil)
    85  	err = s.unit.Destroy()
    86  	c.Assert(err, gc.IsNil)
    87  	asserter.AssertClosed()
    88  
    89  	// Another irrelevant change.
    90  	err = s.unit.ClearResolved()
    91  	c.Assert(err, gc.IsNil)
    92  	asserter.AssertClosed()
    93  
    94  	// Set dead.
    95  	err = s.unit.EnsureDead()
    96  	c.Assert(err, gc.IsNil)
    97  	s.assertAgentTerminates(c, f)
    98  }
    99  
   100  func (s *FilterSuite) TestUnitRemoval(c *gc.C) {
   101  	f, err := newFilter(s.uniter, s.unit.Tag())
   102  	c.Assert(err, gc.IsNil)
   103  	defer f.Stop() // no AssertStop, we test for an error below
   104  
   105  	// short-circuit to remove because no status set.
   106  	err = s.unit.Destroy()
   107  	c.Assert(err, gc.IsNil)
   108  	s.assertAgentTerminates(c, f)
   109  }
   110  
   111  // Ensure we get a signal on f.Dead()
   112  func (s *FilterSuite) assertFilterDies(c *gc.C, f *filter) {
   113  	asserter := coretesting.NotifyAsserterC{
   114  		Precond: func() { s.BackingState.StartSync() },
   115  		C:       c,
   116  		Chan:    f.Dead(),
   117  	}
   118  	asserter.AssertClosed()
   119  }
   120  
   121  func (s *FilterSuite) assertAgentTerminates(c *gc.C, f *filter) {
   122  	s.assertFilterDies(c, f)
   123  	c.Assert(f.Wait(), gc.Equals, worker.ErrTerminateAgent)
   124  }
   125  
   126  func (s *FilterSuite) TestServiceDeath(c *gc.C) {
   127  	f, err := newFilter(s.uniter, s.unit.Tag())
   128  	c.Assert(err, gc.IsNil)
   129  	defer statetesting.AssertStop(c, f)
   130  	dyingAsserter := coretesting.NotifyAsserterC{
   131  		C:       c,
   132  		Precond: func() { s.BackingState.StartSync() },
   133  		Chan:    f.UnitDying(),
   134  	}
   135  	dyingAsserter.AssertNoReceive()
   136  
   137  	err = s.unit.SetStatus(params.StatusStarted, "", nil)
   138  	c.Assert(err, gc.IsNil)
   139  	err = s.wordpress.Destroy()
   140  	c.Assert(err, gc.IsNil)
   141  
   142  	timeout := time.After(coretesting.LongWait)
   143  loop:
   144  	for {
   145  		select {
   146  		case <-f.UnitDying():
   147  			break loop
   148  		case <-time.After(coretesting.ShortWait):
   149  			s.BackingState.StartSync()
   150  		case <-timeout:
   151  			c.Fatalf("dead not detected")
   152  		}
   153  	}
   154  	err = s.unit.Refresh()
   155  	c.Assert(err, gc.IsNil)
   156  	c.Assert(s.unit.Life(), gc.Equals, state.Dying)
   157  
   158  	// Can't set s.wordpress to Dead while it still has units.
   159  }
   160  
   161  func (s *FilterSuite) TestResolvedEvents(c *gc.C) {
   162  	f, err := newFilter(s.uniter, s.unit.Tag())
   163  	c.Assert(err, gc.IsNil)
   164  	defer statetesting.AssertStop(c, f)
   165  
   166  	resolvedAsserter := coretesting.ContentAsserterC{
   167  		C:       c,
   168  		Precond: func() { s.BackingState.StartSync() },
   169  		Chan:    f.ResolvedEvents(),
   170  	}
   171  	resolvedAsserter.AssertNoReceive()
   172  
   173  	// Request an event; no interesting event is available.
   174  	f.WantResolvedEvent()
   175  	resolvedAsserter.AssertNoReceive()
   176  
   177  	// Change the unit in an irrelevant way; no events.
   178  	err = s.unit.SetStatus(params.StatusError, "blarg", nil)
   179  	c.Assert(err, gc.IsNil)
   180  	resolvedAsserter.AssertNoReceive()
   181  
   182  	// Change the unit's resolved to an interesting value; new event received.
   183  	err = s.unit.SetResolved(state.ResolvedRetryHooks)
   184  	c.Assert(err, gc.IsNil)
   185  	assertChange := func(expect params.ResolvedMode) {
   186  		rm := resolvedAsserter.AssertOneReceive().(params.ResolvedMode)
   187  		c.Assert(rm, gc.Equals, expect)
   188  	}
   189  	assertChange(params.ResolvedRetryHooks)
   190  
   191  	// Ask for the event again, and check it's resent.
   192  	f.WantResolvedEvent()
   193  	assertChange(params.ResolvedRetryHooks)
   194  
   195  	// Clear the resolved status *via the filter*; check not resent...
   196  	err = f.ClearResolved()
   197  	c.Assert(err, gc.IsNil)
   198  	resolvedAsserter.AssertNoReceive()
   199  
   200  	// ...even when requested.
   201  	f.WantResolvedEvent()
   202  	resolvedAsserter.AssertNoReceive()
   203  
   204  	// Induce several events; only latest state is reported.
   205  	err = s.unit.SetResolved(state.ResolvedRetryHooks)
   206  	c.Assert(err, gc.IsNil)
   207  	err = f.ClearResolved()
   208  	c.Assert(err, gc.IsNil)
   209  	err = s.unit.SetResolved(state.ResolvedNoHooks)
   210  	c.Assert(err, gc.IsNil)
   211  	assertChange(params.ResolvedNoHooks)
   212  }
   213  
   214  func (s *FilterSuite) TestCharmUpgradeEvents(c *gc.C) {
   215  	oldCharm := s.AddTestingCharm(c, "upgrade1")
   216  	svc := s.AddTestingService(c, "upgradetest", oldCharm)
   217  	unit, err := svc.AddUnit()
   218  	c.Assert(err, gc.IsNil)
   219  
   220  	s.APILogin(c, unit)
   221  
   222  	f, err := newFilter(s.uniter, unit.Tag())
   223  	c.Assert(err, gc.IsNil)
   224  	defer statetesting.AssertStop(c, f)
   225  
   226  	// No initial event is sent.
   227  	assertNoChange := func() {
   228  		s.BackingState.StartSync()
   229  		select {
   230  		case sch := <-f.UpgradeEvents():
   231  			c.Fatalf("unexpected %#v", sch)
   232  		case <-time.After(coretesting.ShortWait):
   233  		}
   234  	}
   235  	assertNoChange()
   236  
   237  	// Setting a charm generates no new events if it already matches.
   238  	err = f.SetCharm(oldCharm.URL())
   239  	c.Assert(err, gc.IsNil)
   240  	assertNoChange()
   241  
   242  	// Explicitly request an event relative to the existing state; nothing.
   243  	f.WantUpgradeEvent(false)
   244  	assertNoChange()
   245  
   246  	// Change the service in an irrelevant way; no events.
   247  	err = svc.SetExposed()
   248  	c.Assert(err, gc.IsNil)
   249  	assertNoChange()
   250  
   251  	// Change the service's charm; new event received.
   252  	newCharm := s.AddTestingCharm(c, "upgrade2")
   253  	err = svc.SetCharm(newCharm, false)
   254  	c.Assert(err, gc.IsNil)
   255  	assertChange := func(url *charm.URL) {
   256  		s.BackingState.StartSync()
   257  		select {
   258  		case upgradeCharm := <-f.UpgradeEvents():
   259  			c.Assert(upgradeCharm, gc.DeepEquals, url)
   260  		case <-time.After(coretesting.LongWait):
   261  			c.Fatalf("timed out")
   262  		}
   263  	}
   264  	assertChange(newCharm.URL())
   265  	assertNoChange()
   266  
   267  	// Request a new upgrade *unforced* upgrade event, we should see one.
   268  	f.WantUpgradeEvent(false)
   269  	assertChange(newCharm.URL())
   270  	assertNoChange()
   271  
   272  	// Request only *forced* upgrade events; nothing.
   273  	f.WantUpgradeEvent(true)
   274  	assertNoChange()
   275  
   276  	// But when we have a forced upgrade to the same URL, no new event.
   277  	err = svc.SetCharm(oldCharm, true)
   278  	c.Assert(err, gc.IsNil)
   279  	assertNoChange()
   280  
   281  	// ...but a *forced* change to a different URL should generate an event.
   282  	err = svc.SetCharm(newCharm, true)
   283  	assertChange(newCharm.URL())
   284  	assertNoChange()
   285  }
   286  
   287  func (s *FilterSuite) TestConfigEvents(c *gc.C) {
   288  	f, err := newFilter(s.uniter, s.unit.Tag())
   289  	c.Assert(err, gc.IsNil)
   290  	defer statetesting.AssertStop(c, f)
   291  
   292  	// Test no changes before the charm URL is set.
   293  	assertNoChange := func() {
   294  		s.BackingState.StartSync()
   295  		select {
   296  		case <-f.ConfigEvents():
   297  			c.Fatalf("unexpected config event")
   298  		case <-time.After(coretesting.ShortWait):
   299  		}
   300  	}
   301  	assertNoChange()
   302  
   303  	// Set the charm URL to trigger config events.
   304  	err = f.SetCharm(s.wpcharm.URL())
   305  	c.Assert(err, gc.IsNil)
   306  	assertChange := func() {
   307  		s.BackingState.StartSync()
   308  		select {
   309  		case _, ok := <-f.ConfigEvents():
   310  			c.Assert(ok, gc.Equals, true)
   311  		case <-time.After(coretesting.LongWait):
   312  			c.Fatalf("timed out")
   313  		}
   314  		assertNoChange()
   315  	}
   316  	assertChange()
   317  
   318  	// Change the config; new event received.
   319  	changeConfig := func(title interface{}) {
   320  		err := s.wordpress.UpdateConfigSettings(charm.Settings{
   321  			"blog-title": title,
   322  		})
   323  		c.Assert(err, gc.IsNil)
   324  	}
   325  	changeConfig("20,000 leagues in the cloud")
   326  	assertChange()
   327  
   328  	// Change the config a few more times, then reset the events. We sync to
   329  	// make sure the events have arrived in the watcher -- and then wait a
   330  	// little longer, to allow for the delay while the events are coalesced
   331  	// -- before we tell it to discard all received events. This would be
   332  	// much better tested by controlling a mocked-out watcher directly, but
   333  	// that's a bit inconvenient for this change.
   334  	changeConfig(nil)
   335  	changeConfig("the curious incident of the dog in the cloud")
   336  	s.BackingState.StartSync()
   337  	time.Sleep(250 * time.Millisecond)
   338  	f.DiscardConfigEvent()
   339  	assertNoChange()
   340  
   341  	// Check that a filter's initial event works with DiscardConfigEvent
   342  	// as expected.
   343  	f, err = newFilter(s.uniter, s.unit.Tag())
   344  	c.Assert(err, gc.IsNil)
   345  	defer statetesting.AssertStop(c, f)
   346  	s.BackingState.StartSync()
   347  	f.DiscardConfigEvent()
   348  	assertNoChange()
   349  
   350  	// Further changes are still collapsed as appropriate.
   351  	changeConfig("forsooth")
   352  	changeConfig("imagination failure")
   353  	assertChange()
   354  }
   355  
   356  func (s *FilterSuite) TestCharmErrorEvents(c *gc.C) {
   357  	f, err := newFilter(s.uniter, s.unit.Tag())
   358  	c.Assert(err, gc.IsNil)
   359  	defer f.Stop() // no AssertStop, we test for an error below
   360  
   361  	assertNoChange := func() {
   362  		s.BackingState.StartSync()
   363  		select {
   364  		case <-f.ConfigEvents():
   365  			c.Fatalf("unexpected config event")
   366  		case <-time.After(coretesting.ShortWait):
   367  		}
   368  	}
   369  
   370  	// Check setting an invalid charm URL does not send events.
   371  	err = f.SetCharm(charm.MustParseURL("cs:missing/one-1"))
   372  	c.Assert(err, gc.Equals, tomb.ErrDying)
   373  	assertNoChange()
   374  	s.assertFilterDies(c, f)
   375  
   376  	// Filter died after the error, so restart it.
   377  	f, err = newFilter(s.uniter, s.unit.Tag())
   378  	c.Assert(err, gc.IsNil)
   379  	defer f.Stop() // no AssertStop, we test for an error below
   380  
   381  	// Check with a nil charm URL, again no changes.
   382  	err = f.SetCharm(nil)
   383  	c.Assert(err, gc.Equals, tomb.ErrDying)
   384  	assertNoChange()
   385  	s.assertFilterDies(c, f)
   386  }
   387  
   388  func (s *FilterSuite) TestRelationsEvents(c *gc.C) {
   389  	f, err := newFilter(s.uniter, s.unit.Tag())
   390  	c.Assert(err, gc.IsNil)
   391  	defer statetesting.AssertStop(c, f)
   392  
   393  	assertNoChange := func() {
   394  		s.BackingState.StartSync()
   395  		select {
   396  		case ids := <-f.RelationsEvents():
   397  			c.Fatalf("unexpected relations event %#v", ids)
   398  		case <-time.After(coretesting.ShortWait):
   399  		}
   400  	}
   401  	assertNoChange()
   402  
   403  	// Add a couple of relations; check the event.
   404  	rel0 := s.addRelation(c)
   405  	rel1 := s.addRelation(c)
   406  	assertChange := func(expect []int) {
   407  		s.BackingState.StartSync()
   408  		select {
   409  		case got := <-f.RelationsEvents():
   410  			c.Assert(got, gc.DeepEquals, expect)
   411  		case <-time.After(coretesting.LongWait):
   412  			c.Fatalf("timed out")
   413  		}
   414  		assertNoChange()
   415  	}
   416  	assertChange([]int{0, 1})
   417  
   418  	// Add another relation, and change another's Life (by entering scope before
   419  	// Destroy, thereby setting the relation to Dying); check event.
   420  	s.addRelation(c)
   421  	ru0, err := rel0.Unit(s.unit)
   422  	c.Assert(err, gc.IsNil)
   423  	err = ru0.EnterScope(nil)
   424  	c.Assert(err, gc.IsNil)
   425  	err = rel0.Destroy()
   426  	c.Assert(err, gc.IsNil)
   427  	assertChange([]int{0, 2})
   428  
   429  	// Remove a relation completely; check no event, because the relation
   430  	// could not have been removed if the unit was in scope, and therefore
   431  	// the uniter never needs to hear about it.
   432  	err = rel1.Destroy()
   433  	c.Assert(err, gc.IsNil)
   434  	assertNoChange()
   435  	err = f.Stop()
   436  	c.Assert(err, gc.IsNil)
   437  
   438  	// Start a new filter, check initial event.
   439  	f, err = newFilter(s.uniter, s.unit.Tag())
   440  	c.Assert(err, gc.IsNil)
   441  	defer statetesting.AssertStop(c, f)
   442  	assertChange([]int{0, 2})
   443  
   444  	// Check setting the charm URL generates all new relation events.
   445  	err = f.SetCharm(s.wpcharm.URL())
   446  	c.Assert(err, gc.IsNil)
   447  	assertChange([]int{0, 2})
   448  }
   449  
   450  func (s *FilterSuite) addRelation(c *gc.C) *state.Relation {
   451  	if s.mysqlcharm == nil {
   452  		s.mysqlcharm = s.AddTestingCharm(c, "mysql")
   453  	}
   454  	rels, err := s.wordpress.Relations()
   455  	c.Assert(err, gc.IsNil)
   456  	svcName := fmt.Sprintf("mysql%d", len(rels))
   457  	s.AddTestingService(c, svcName, s.mysqlcharm)
   458  	eps, err := s.State.InferEndpoints([]string{svcName, "wordpress"})
   459  	c.Assert(err, gc.IsNil)
   460  	rel, err := s.State.AddRelation(eps...)
   461  	c.Assert(err, gc.IsNil)
   462  	return rel
   463  }