github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/worker/uniter/runner/context/context_test.go (about)

     1  // Copyright 2012-2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package context_test
     5  
     6  import (
     7  	"errors"
     8  	"time"
     9  
    10  	"github.com/juju/testing"
    11  	jc "github.com/juju/testing/checkers"
    12  	gc "gopkg.in/check.v1"
    13  	"gopkg.in/juju/charm.v6-unstable"
    14  
    15  	"github.com/juju/juju/apiserver/params"
    16  	"github.com/juju/juju/network"
    17  	"github.com/juju/juju/status"
    18  	"github.com/juju/juju/worker/uniter/runner"
    19  	"github.com/juju/juju/worker/uniter/runner/context"
    20  	"github.com/juju/juju/worker/uniter/runner/jujuc"
    21  )
    22  
    23  type InterfaceSuite struct {
    24  	HookContextSuite
    25  	stub testing.Stub
    26  }
    27  
    28  var _ = gc.Suite(&InterfaceSuite{})
    29  
    30  func (s *InterfaceSuite) TestUnitName(c *gc.C) {
    31  	ctx := s.GetContext(c, -1, "")
    32  	c.Assert(ctx.UnitName(), gc.Equals, "u/0")
    33  }
    34  
    35  func (s *InterfaceSuite) TestHookRelation(c *gc.C) {
    36  	ctx := s.GetContext(c, -1, "")
    37  	r, err := ctx.HookRelation()
    38  	c.Assert(err, gc.ErrorMatches, ".*")
    39  	c.Assert(r, gc.IsNil)
    40  }
    41  
    42  func (s *InterfaceSuite) TestRemoteUnitName(c *gc.C) {
    43  	ctx := s.GetContext(c, -1, "")
    44  	name, err := ctx.RemoteUnitName()
    45  	c.Assert(err, gc.ErrorMatches, ".*")
    46  	c.Assert(name, gc.Equals, "")
    47  }
    48  
    49  func (s *InterfaceSuite) TestRelationIds(c *gc.C) {
    50  	ctx := s.GetContext(c, -1, "")
    51  	relIds, err := ctx.RelationIds()
    52  	c.Assert(err, jc.ErrorIsNil)
    53  	c.Assert(relIds, gc.HasLen, 2)
    54  	r, err := ctx.Relation(0)
    55  	c.Assert(err, jc.ErrorIsNil)
    56  	c.Assert(r.Name(), gc.Equals, "db")
    57  	c.Assert(r.FakeId(), gc.Equals, "db:0")
    58  	r, err = ctx.Relation(123)
    59  	c.Assert(err, gc.ErrorMatches, ".*")
    60  	c.Assert(r, gc.IsNil)
    61  }
    62  
    63  func (s *InterfaceSuite) TestRelationContext(c *gc.C) {
    64  	ctx := s.GetContext(c, 1, "")
    65  	r, err := ctx.HookRelation()
    66  	c.Assert(err, jc.ErrorIsNil)
    67  	c.Assert(r.Name(), gc.Equals, "db")
    68  	c.Assert(r.FakeId(), gc.Equals, "db:1")
    69  }
    70  
    71  func (s *InterfaceSuite) TestRelationContextWithRemoteUnitName(c *gc.C) {
    72  	ctx := s.GetContext(c, 1, "u/123")
    73  	name, err := ctx.RemoteUnitName()
    74  	c.Assert(err, jc.ErrorIsNil)
    75  	c.Assert(name, gc.Equals, "u/123")
    76  }
    77  
    78  func (s *InterfaceSuite) TestAddingMetricsInWrongContext(c *gc.C) {
    79  	ctx := s.GetContext(c, 1, "u/123")
    80  	err := ctx.AddMetric("key", "123", time.Now())
    81  	c.Assert(err, gc.ErrorMatches, "metrics not allowed in this context")
    82  }
    83  
    84  func (s *InterfaceSuite) TestAvailabilityZone(c *gc.C) {
    85  	ctx := s.GetContext(c, -1, "")
    86  	zone, err := ctx.AvailabilityZone()
    87  	c.Check(err, jc.ErrorIsNil)
    88  	c.Check(zone, gc.Equals, "a-zone")
    89  }
    90  
    91  func (s *InterfaceSuite) TestUnitNetworkConfig(c *gc.C) {
    92  	// Only the error case is tested to ensure end-to-end integration, the rest
    93  	// of the cases are tested separately for network-get, api/uniter, and
    94  	// apiserver/uniter, respectively.
    95  	ctx := s.GetContext(c, -1, "")
    96  	netConfig, err := ctx.NetworkConfig("unknown")
    97  	c.Check(err, gc.ErrorMatches, `binding name "unknown" not defined by the unit's charm`)
    98  	c.Check(netConfig, gc.IsNil)
    99  }
   100  
   101  func (s *InterfaceSuite) TestUnitStatus(c *gc.C) {
   102  	ctx := s.GetContext(c, -1, "")
   103  	defer context.PatchCachedStatus(ctx.(runner.Context), "maintenance", "working", map[string]interface{}{"hello": "world"})()
   104  	status, err := ctx.UnitStatus()
   105  	c.Check(err, jc.ErrorIsNil)
   106  	c.Check(status.Status, gc.Equals, "maintenance")
   107  	c.Check(status.Info, gc.Equals, "working")
   108  	c.Check(status.Data, gc.DeepEquals, map[string]interface{}{"hello": "world"})
   109  }
   110  
   111  func (s *InterfaceSuite) TestSetUnitStatus(c *gc.C) {
   112  	ctx := s.GetContext(c, -1, "")
   113  	status := jujuc.StatusInfo{
   114  		Status: "maintenance",
   115  		Info:   "doing work",
   116  	}
   117  	err := ctx.SetUnitStatus(status)
   118  	c.Check(err, jc.ErrorIsNil)
   119  	unitStatus, err := ctx.UnitStatus()
   120  	c.Check(err, jc.ErrorIsNil)
   121  	c.Check(unitStatus.Status, gc.Equals, "maintenance")
   122  	c.Check(unitStatus.Info, gc.Equals, "doing work")
   123  	c.Check(unitStatus.Data, gc.DeepEquals, map[string]interface{}{})
   124  }
   125  
   126  func (s *InterfaceSuite) TestSetUnitStatusUpdatesFlag(c *gc.C) {
   127  	ctx := s.GetContext(c, -1, "")
   128  	c.Assert(ctx.(runner.Context).HasExecutionSetUnitStatus(), jc.IsFalse)
   129  	status := jujuc.StatusInfo{
   130  		Status: "maintenance",
   131  		Info:   "doing work",
   132  	}
   133  	err := ctx.SetUnitStatus(status)
   134  	c.Check(err, jc.ErrorIsNil)
   135  	c.Assert(ctx.(runner.Context).HasExecutionSetUnitStatus(), jc.IsTrue)
   136  }
   137  
   138  func (s *InterfaceSuite) TestGetSetWorkloadVersion(c *gc.C) {
   139  	ctx := s.GetContext(c, -1, "")
   140  	// No workload version set yet.
   141  	result, err := ctx.UnitWorkloadVersion()
   142  	c.Assert(result, gc.Equals, "")
   143  	c.Assert(err, jc.ErrorIsNil)
   144  
   145  	err = ctx.SetUnitWorkloadVersion("Pipey")
   146  	c.Assert(err, jc.ErrorIsNil)
   147  
   148  	result, err = ctx.UnitWorkloadVersion()
   149  	c.Assert(err, jc.ErrorIsNil)
   150  	c.Assert(result, gc.Equals, "Pipey")
   151  }
   152  
   153  func (s *InterfaceSuite) TestUnitStatusCaching(c *gc.C) {
   154  	ctx := s.GetContext(c, -1, "")
   155  	unitStatus, err := ctx.UnitStatus()
   156  	c.Check(err, jc.ErrorIsNil)
   157  	c.Check(unitStatus.Status, gc.Equals, "waiting")
   158  	c.Check(unitStatus.Info, gc.Equals, "waiting for machine")
   159  	c.Check(unitStatus.Data, gc.DeepEquals, map[string]interface{}{})
   160  
   161  	// Change remote state.
   162  	now := time.Now()
   163  	sInfo := status.StatusInfo{
   164  		Status:  status.Active,
   165  		Message: "it works",
   166  		Since:   &now,
   167  	}
   168  	err = s.unit.SetStatus(sInfo)
   169  	c.Assert(err, jc.ErrorIsNil)
   170  
   171  	// Local view is unchanged.
   172  	unitStatus, err = ctx.UnitStatus()
   173  	c.Check(err, jc.ErrorIsNil)
   174  	c.Check(unitStatus.Status, gc.Equals, "waiting")
   175  	c.Check(unitStatus.Info, gc.Equals, "waiting for machine")
   176  	c.Check(unitStatus.Data, gc.DeepEquals, map[string]interface{}{})
   177  }
   178  
   179  func (s *InterfaceSuite) TestUnitCaching(c *gc.C) {
   180  	ctx := s.GetContext(c, -1, "")
   181  	pr, err := ctx.PrivateAddress()
   182  	c.Assert(err, jc.ErrorIsNil)
   183  	c.Assert(pr, gc.Equals, "u-0.testing.invalid")
   184  	pa, err := ctx.PublicAddress()
   185  	c.Assert(err, jc.ErrorIsNil)
   186  	// Initially the public address is the same as the private address since
   187  	// the "most public" address is chosen.
   188  	c.Assert(pr, gc.Equals, pa)
   189  
   190  	// Change remote state.
   191  	err = s.machine.SetProviderAddresses(
   192  		network.NewScopedAddress("blah.testing.invalid", network.ScopePublic),
   193  	)
   194  	c.Assert(err, jc.ErrorIsNil)
   195  
   196  	// Local view is unchanged.
   197  	pr, err = ctx.PrivateAddress()
   198  	c.Assert(err, jc.ErrorIsNil)
   199  	c.Assert(pr, gc.Equals, "u-0.testing.invalid")
   200  	pa, err = ctx.PublicAddress()
   201  	c.Assert(err, jc.ErrorIsNil)
   202  	c.Assert(pr, gc.Equals, pa)
   203  }
   204  
   205  func (s *InterfaceSuite) TestConfigCaching(c *gc.C) {
   206  	ctx := s.GetContext(c, -1, "")
   207  	settings, err := ctx.ConfigSettings()
   208  	c.Assert(err, jc.ErrorIsNil)
   209  	c.Assert(settings, gc.DeepEquals, charm.Settings{"blog-title": "My Title"})
   210  
   211  	// Change remote config.
   212  	err = s.service.UpdateConfigSettings(charm.Settings{
   213  		"blog-title": "Something Else",
   214  	})
   215  	c.Assert(err, jc.ErrorIsNil)
   216  
   217  	// Local view is not changed.
   218  	settings, err = ctx.ConfigSettings()
   219  	c.Assert(err, jc.ErrorIsNil)
   220  	c.Assert(settings, gc.DeepEquals, charm.Settings{"blog-title": "My Title"})
   221  }
   222  
   223  // TestNonActionCallsToActionMethodsFail does exactly what its name says:
   224  // it simply makes sure that Action-related calls to HookContexts with a nil
   225  // actionData member error out correctly.
   226  func (s *InterfaceSuite) TestNonActionCallsToActionMethodsFail(c *gc.C) {
   227  	ctx := context.HookContext{}
   228  	_, err := ctx.ActionParams()
   229  	c.Check(err, gc.ErrorMatches, "not running an action")
   230  	err = ctx.SetActionFailed()
   231  	c.Check(err, gc.ErrorMatches, "not running an action")
   232  	err = ctx.SetActionMessage("foo")
   233  	c.Check(err, gc.ErrorMatches, "not running an action")
   234  	err = ctx.UpdateActionResults([]string{"1", "2", "3"}, "value")
   235  	c.Check(err, gc.ErrorMatches, "not running an action")
   236  }
   237  
   238  // TestUpdateActionResults demonstrates that UpdateActionResults functions
   239  // as expected.
   240  func (s *InterfaceSuite) TestUpdateActionResults(c *gc.C) {
   241  	tests := []struct {
   242  		initial  map[string]interface{}
   243  		keys     []string
   244  		value    string
   245  		expected map[string]interface{}
   246  	}{{
   247  		initial: map[string]interface{}{},
   248  		keys:    []string{"foo"},
   249  		value:   "bar",
   250  		expected: map[string]interface{}{
   251  			"foo": "bar",
   252  		},
   253  	}, {
   254  		initial: map[string]interface{}{
   255  			"foo": "bar",
   256  		},
   257  		keys:  []string{"foo", "bar"},
   258  		value: "baz",
   259  		expected: map[string]interface{}{
   260  			"foo": map[string]interface{}{
   261  				"bar": "baz",
   262  			},
   263  		},
   264  	}, {
   265  		initial: map[string]interface{}{
   266  			"foo": map[string]interface{}{
   267  				"bar": "baz",
   268  			},
   269  		},
   270  		keys:  []string{"foo"},
   271  		value: "bar",
   272  		expected: map[string]interface{}{
   273  			"foo": "bar",
   274  		},
   275  	}}
   276  
   277  	for i, t := range tests {
   278  		c.Logf("UpdateActionResults test %d: %#v: %#v", i, t.keys, t.value)
   279  		hctx := context.GetStubActionContext(t.initial)
   280  		err := hctx.UpdateActionResults(t.keys, t.value)
   281  		c.Assert(err, jc.ErrorIsNil)
   282  		actionData, err := hctx.ActionData()
   283  		c.Assert(err, jc.ErrorIsNil)
   284  		c.Assert(actionData.ResultsMap, jc.DeepEquals, t.expected)
   285  	}
   286  }
   287  
   288  // TestSetActionFailed ensures SetActionFailed works properly.
   289  func (s *InterfaceSuite) TestSetActionFailed(c *gc.C) {
   290  	hctx := context.GetStubActionContext(nil)
   291  	err := hctx.SetActionFailed()
   292  	c.Assert(err, jc.ErrorIsNil)
   293  	actionData, err := hctx.ActionData()
   294  	c.Assert(err, jc.ErrorIsNil)
   295  	c.Check(actionData.Failed, jc.IsTrue)
   296  }
   297  
   298  // TestSetActionMessage ensures SetActionMessage works properly.
   299  func (s *InterfaceSuite) TestSetActionMessage(c *gc.C) {
   300  	hctx := context.GetStubActionContext(nil)
   301  	err := hctx.SetActionMessage("because reasons")
   302  	c.Assert(err, jc.ErrorIsNil)
   303  	actionData, err := hctx.ActionData()
   304  	c.Check(err, jc.ErrorIsNil)
   305  	c.Check(actionData.ResultsMessage, gc.Equals, "because reasons")
   306  }
   307  
   308  func (s *InterfaceSuite) TestRequestRebootAfterHook(c *gc.C) {
   309  	var killed bool
   310  	p := &mockProcess{func() error {
   311  		killed = true
   312  		return nil
   313  	}}
   314  	ctx := s.GetContext(c, -1, "").(*context.HookContext)
   315  	ctx.SetProcess(p)
   316  	err := ctx.RequestReboot(jujuc.RebootAfterHook)
   317  	c.Assert(err, jc.ErrorIsNil)
   318  	c.Assert(killed, jc.IsFalse)
   319  	priority := ctx.GetRebootPriority()
   320  	c.Assert(priority, gc.Equals, jujuc.RebootAfterHook)
   321  }
   322  
   323  func (s *InterfaceSuite) TestRequestRebootNow(c *gc.C) {
   324  	ctx := s.GetContext(c, -1, "").(*context.HookContext)
   325  
   326  	var stub testing.Stub
   327  	var p *mockProcess
   328  	p = &mockProcess{func() error {
   329  		// Reboot priority should be set before the process
   330  		// is killed, or else the client waiting for the
   331  		// process to exit will race with the setting of
   332  		// the priority.
   333  		priority := ctx.GetRebootPriority()
   334  		c.Assert(priority, gc.Equals, jujuc.RebootNow)
   335  		return stub.NextErr()
   336  	}}
   337  	stub.SetErrors(errors.New("process is already dead"))
   338  	ctx.SetProcess(p)
   339  
   340  	err := ctx.RequestReboot(jujuc.RebootNow)
   341  	c.Assert(err, jc.ErrorIsNil)
   342  
   343  	// Everything went well, so priority should still be RebootNow.
   344  	priority := ctx.GetRebootPriority()
   345  	c.Assert(priority, gc.Equals, jujuc.RebootNow)
   346  }
   347  
   348  func (s *InterfaceSuite) TestRequestRebootNowTimeout(c *gc.C) {
   349  	ctx := s.GetContext(c, -1, "").(*context.HookContext)
   350  
   351  	var advanced bool
   352  	var p *mockProcess
   353  	p = &mockProcess{func() error {
   354  		// Reboot priority should be set before the process
   355  		// is killed, or else the client waiting for the
   356  		// process to exit will race with the setting of
   357  		// the priority.
   358  		priority := ctx.GetRebootPriority()
   359  		c.Assert(priority, gc.Equals, jujuc.RebootNow)
   360  		if !advanced {
   361  			advanced = true
   362  			s.clock.Advance(time.Hour) // force timeout
   363  		}
   364  		return nil
   365  	}}
   366  	ctx.SetProcess(p)
   367  
   368  	err := ctx.RequestReboot(jujuc.RebootNow)
   369  	c.Assert(err, gc.ErrorMatches, "failed to kill context process 123")
   370  
   371  	// RequestReboot failed, so priority should revert to RebootSkip.
   372  	priority := ctx.GetRebootPriority()
   373  	c.Assert(priority, gc.Equals, jujuc.RebootSkip)
   374  }
   375  
   376  func (s *InterfaceSuite) TestRequestRebootNowNoProcess(c *gc.C) {
   377  	// A normal hook run or a juju-run command will record the *os.Process
   378  	// object of the running command, in HookContext. When requesting a
   379  	// reboot with the --now flag, the process is killed and only
   380  	// then will we set the reboot priority. This test basically simulates
   381  	// the case when the process calling juju-reboot is not recorded.
   382  	ctx := context.HookContext{}
   383  	err := ctx.RequestReboot(jujuc.RebootNow)
   384  	c.Assert(err, gc.ErrorMatches, "no process to kill")
   385  	priority := ctx.GetRebootPriority()
   386  	c.Assert(priority, gc.Equals, jujuc.RebootNow)
   387  }
   388  
   389  func (s *InterfaceSuite) TestStorageAddConstraints(c *gc.C) {
   390  	expected := map[string][]params.StorageConstraints{
   391  		"data": []params.StorageConstraints{
   392  			params.StorageConstraints{},
   393  		},
   394  	}
   395  
   396  	ctx := context.HookContext{}
   397  	addStorageToContext(&ctx, "data", params.StorageConstraints{})
   398  	assertStorageAddInContext(c, ctx, expected)
   399  }
   400  
   401  var two = uint64(2)
   402  
   403  func (s *InterfaceSuite) TestStorageAddConstraintsSameStorage(c *gc.C) {
   404  	expected := map[string][]params.StorageConstraints{
   405  		"data": []params.StorageConstraints{
   406  			params.StorageConstraints{},
   407  			params.StorageConstraints{Count: &two},
   408  		},
   409  	}
   410  
   411  	ctx := context.HookContext{}
   412  	addStorageToContext(&ctx, "data", params.StorageConstraints{})
   413  	addStorageToContext(&ctx, "data", params.StorageConstraints{Count: &two})
   414  	assertStorageAddInContext(c, ctx, expected)
   415  }
   416  
   417  func (s *InterfaceSuite) TestStorageAddConstraintsDifferentStorage(c *gc.C) {
   418  	expected := map[string][]params.StorageConstraints{
   419  		"data": []params.StorageConstraints{params.StorageConstraints{}},
   420  		"diff": []params.StorageConstraints{
   421  			params.StorageConstraints{Count: &two}},
   422  	}
   423  
   424  	ctx := context.HookContext{}
   425  	addStorageToContext(&ctx, "data", params.StorageConstraints{})
   426  	addStorageToContext(&ctx, "diff", params.StorageConstraints{Count: &two})
   427  	assertStorageAddInContext(c, ctx, expected)
   428  }
   429  
   430  func addStorageToContext(ctx *context.HookContext,
   431  	name string,
   432  	cons params.StorageConstraints,
   433  ) {
   434  	addOne := map[string]params.StorageConstraints{name: cons}
   435  	ctx.AddUnitStorage(addOne)
   436  }
   437  
   438  func assertStorageAddInContext(c *gc.C,
   439  	ctx context.HookContext, expected map[string][]params.StorageConstraints,
   440  ) {
   441  	obtained := context.StorageAddConstraints(&ctx)
   442  	c.Assert(len(obtained), gc.Equals, len(expected))
   443  	for k, v := range obtained {
   444  		c.Assert(v, jc.SameContents, expected[k])
   445  	}
   446  }
   447  
   448  type mockProcess struct {
   449  	kill func() error
   450  }
   451  
   452  func (p *mockProcess) Kill() error {
   453  	return p.kill()
   454  }
   455  
   456  func (p *mockProcess) Pid() int {
   457  	return 123
   458  }