github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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) TestUnitStatusCaching(c *gc.C) {
   139  	ctx := s.GetContext(c, -1, "")
   140  	unitStatus, err := ctx.UnitStatus()
   141  	c.Check(err, jc.ErrorIsNil)
   142  	c.Check(unitStatus.Status, gc.Equals, "unknown")
   143  	c.Check(unitStatus.Data, gc.DeepEquals, map[string]interface{}{})
   144  
   145  	// Change remote state.
   146  	err = s.unit.SetStatus(status.StatusActive, "it works", nil)
   147  	c.Assert(err, jc.ErrorIsNil)
   148  
   149  	// Local view is unchanged.
   150  	unitStatus, err = ctx.UnitStatus()
   151  	c.Check(err, jc.ErrorIsNil)
   152  	c.Check(unitStatus.Status, gc.Equals, "unknown")
   153  	c.Check(unitStatus.Data, gc.DeepEquals, map[string]interface{}{})
   154  }
   155  
   156  func (s *InterfaceSuite) TestUnitCaching(c *gc.C) {
   157  	ctx := s.GetContext(c, -1, "")
   158  	pr, err := ctx.PrivateAddress()
   159  	c.Assert(err, jc.ErrorIsNil)
   160  	c.Assert(pr, gc.Equals, "u-0.testing.invalid")
   161  	pa, err := ctx.PublicAddress()
   162  	c.Assert(err, jc.ErrorIsNil)
   163  	// Initially the public address is the same as the private address since
   164  	// the "most public" address is chosen.
   165  	c.Assert(pr, gc.Equals, pa)
   166  
   167  	// Change remote state.
   168  	err = s.machine.SetProviderAddresses(
   169  		network.NewScopedAddress("blah.testing.invalid", network.ScopePublic),
   170  	)
   171  	c.Assert(err, jc.ErrorIsNil)
   172  
   173  	// Local view is unchanged.
   174  	pr, err = ctx.PrivateAddress()
   175  	c.Assert(err, jc.ErrorIsNil)
   176  	c.Assert(pr, gc.Equals, "u-0.testing.invalid")
   177  	pa, err = ctx.PublicAddress()
   178  	c.Assert(err, jc.ErrorIsNil)
   179  	c.Assert(pr, gc.Equals, pa)
   180  }
   181  
   182  func (s *InterfaceSuite) TestConfigCaching(c *gc.C) {
   183  	ctx := s.GetContext(c, -1, "")
   184  	settings, err := ctx.ConfigSettings()
   185  	c.Assert(err, jc.ErrorIsNil)
   186  	c.Assert(settings, gc.DeepEquals, charm.Settings{"blog-title": "My Title"})
   187  
   188  	// Change remote config.
   189  	err = s.service.UpdateConfigSettings(charm.Settings{
   190  		"blog-title": "Something Else",
   191  	})
   192  	c.Assert(err, jc.ErrorIsNil)
   193  
   194  	// Local view is not changed.
   195  	settings, err = ctx.ConfigSettings()
   196  	c.Assert(err, jc.ErrorIsNil)
   197  	c.Assert(settings, gc.DeepEquals, charm.Settings{"blog-title": "My Title"})
   198  }
   199  
   200  // TestNonActionCallsToActionMethodsFail does exactly what its name says:
   201  // it simply makes sure that Action-related calls to HookContexts with a nil
   202  // actionData member error out correctly.
   203  func (s *InterfaceSuite) TestNonActionCallsToActionMethodsFail(c *gc.C) {
   204  	ctx := context.HookContext{}
   205  	_, err := ctx.ActionParams()
   206  	c.Check(err, gc.ErrorMatches, "not running an action")
   207  	err = ctx.SetActionFailed()
   208  	c.Check(err, gc.ErrorMatches, "not running an action")
   209  	err = ctx.SetActionMessage("foo")
   210  	c.Check(err, gc.ErrorMatches, "not running an action")
   211  	err = ctx.UpdateActionResults([]string{"1", "2", "3"}, "value")
   212  	c.Check(err, gc.ErrorMatches, "not running an action")
   213  }
   214  
   215  // TestUpdateActionResults demonstrates that UpdateActionResults functions
   216  // as expected.
   217  func (s *InterfaceSuite) TestUpdateActionResults(c *gc.C) {
   218  	tests := []struct {
   219  		initial  map[string]interface{}
   220  		keys     []string
   221  		value    string
   222  		expected map[string]interface{}
   223  	}{{
   224  		initial: map[string]interface{}{},
   225  		keys:    []string{"foo"},
   226  		value:   "bar",
   227  		expected: map[string]interface{}{
   228  			"foo": "bar",
   229  		},
   230  	}, {
   231  		initial: map[string]interface{}{
   232  			"foo": "bar",
   233  		},
   234  		keys:  []string{"foo", "bar"},
   235  		value: "baz",
   236  		expected: map[string]interface{}{
   237  			"foo": map[string]interface{}{
   238  				"bar": "baz",
   239  			},
   240  		},
   241  	}, {
   242  		initial: map[string]interface{}{
   243  			"foo": map[string]interface{}{
   244  				"bar": "baz",
   245  			},
   246  		},
   247  		keys:  []string{"foo"},
   248  		value: "bar",
   249  		expected: map[string]interface{}{
   250  			"foo": "bar",
   251  		},
   252  	}}
   253  
   254  	for i, t := range tests {
   255  		c.Logf("UpdateActionResults test %d: %#v: %#v", i, t.keys, t.value)
   256  		hctx := context.GetStubActionContext(t.initial)
   257  		err := hctx.UpdateActionResults(t.keys, t.value)
   258  		c.Assert(err, jc.ErrorIsNil)
   259  		actionData, err := hctx.ActionData()
   260  		c.Assert(err, jc.ErrorIsNil)
   261  		c.Assert(actionData.ResultsMap, jc.DeepEquals, t.expected)
   262  	}
   263  }
   264  
   265  // TestSetActionFailed ensures SetActionFailed works properly.
   266  func (s *InterfaceSuite) TestSetActionFailed(c *gc.C) {
   267  	hctx := context.GetStubActionContext(nil)
   268  	err := hctx.SetActionFailed()
   269  	c.Assert(err, jc.ErrorIsNil)
   270  	actionData, err := hctx.ActionData()
   271  	c.Assert(err, jc.ErrorIsNil)
   272  	c.Check(actionData.Failed, jc.IsTrue)
   273  }
   274  
   275  // TestSetActionMessage ensures SetActionMessage works properly.
   276  func (s *InterfaceSuite) TestSetActionMessage(c *gc.C) {
   277  	hctx := context.GetStubActionContext(nil)
   278  	err := hctx.SetActionMessage("because reasons")
   279  	c.Assert(err, jc.ErrorIsNil)
   280  	actionData, err := hctx.ActionData()
   281  	c.Check(err, jc.ErrorIsNil)
   282  	c.Check(actionData.ResultsMessage, gc.Equals, "because reasons")
   283  }
   284  
   285  func (s *InterfaceSuite) TestRequestRebootAfterHook(c *gc.C) {
   286  	var killed bool
   287  	p := &mockProcess{func() error {
   288  		killed = true
   289  		return nil
   290  	}}
   291  	ctx := s.GetContext(c, -1, "").(*context.HookContext)
   292  	ctx.SetProcess(p)
   293  	err := ctx.RequestReboot(jujuc.RebootAfterHook)
   294  	c.Assert(err, jc.ErrorIsNil)
   295  	c.Assert(killed, jc.IsFalse)
   296  	priority := ctx.GetRebootPriority()
   297  	c.Assert(priority, gc.Equals, jujuc.RebootAfterHook)
   298  }
   299  
   300  func (s *InterfaceSuite) TestRequestRebootNow(c *gc.C) {
   301  	ctx := s.GetContext(c, -1, "").(*context.HookContext)
   302  
   303  	var stub testing.Stub
   304  	var p *mockProcess
   305  	p = &mockProcess{func() error {
   306  		// Reboot priority should be set before the process
   307  		// is killed, or else the client waiting for the
   308  		// process to exit will race with the setting of
   309  		// the priority.
   310  		priority := ctx.GetRebootPriority()
   311  		c.Assert(priority, gc.Equals, jujuc.RebootNow)
   312  		return stub.NextErr()
   313  	}}
   314  	stub.SetErrors(errors.New("process is already dead"))
   315  	ctx.SetProcess(p)
   316  
   317  	err := ctx.RequestReboot(jujuc.RebootNow)
   318  	c.Assert(err, jc.ErrorIsNil)
   319  
   320  	// Everything went well, so priority should still be RebootNow.
   321  	priority := ctx.GetRebootPriority()
   322  	c.Assert(priority, gc.Equals, jujuc.RebootNow)
   323  }
   324  
   325  func (s *InterfaceSuite) TestRequestRebootNowTimeout(c *gc.C) {
   326  	ctx := s.GetContext(c, -1, "").(*context.HookContext)
   327  
   328  	var advanced bool
   329  	var p *mockProcess
   330  	p = &mockProcess{func() error {
   331  		// Reboot priority should be set before the process
   332  		// is killed, or else the client waiting for the
   333  		// process to exit will race with the setting of
   334  		// the priority.
   335  		priority := ctx.GetRebootPriority()
   336  		c.Assert(priority, gc.Equals, jujuc.RebootNow)
   337  		if !advanced {
   338  			advanced = true
   339  			s.clock.Advance(time.Hour) // force timeout
   340  		}
   341  		return nil
   342  	}}
   343  	ctx.SetProcess(p)
   344  
   345  	err := ctx.RequestReboot(jujuc.RebootNow)
   346  	c.Assert(err, gc.ErrorMatches, "failed to kill context process 123")
   347  
   348  	// RequestReboot failed, so priority should revert to RebootSkip.
   349  	priority := ctx.GetRebootPriority()
   350  	c.Assert(priority, gc.Equals, jujuc.RebootSkip)
   351  }
   352  
   353  func (s *InterfaceSuite) TestRequestRebootNowNoProcess(c *gc.C) {
   354  	// A normal hook run or a juju-run command will record the *os.Process
   355  	// object of the running command, in HookContext. When requesting a
   356  	// reboot with the --now flag, the process is killed and only
   357  	// then will we set the reboot priority. This test basically simulates
   358  	// the case when the process calling juju-reboot is not recorded.
   359  	ctx := context.HookContext{}
   360  	err := ctx.RequestReboot(jujuc.RebootNow)
   361  	c.Assert(err, gc.ErrorMatches, "no process to kill")
   362  	priority := ctx.GetRebootPriority()
   363  	c.Assert(priority, gc.Equals, jujuc.RebootNow)
   364  }
   365  
   366  func (s *InterfaceSuite) TestStorageAddConstraints(c *gc.C) {
   367  	expected := map[string][]params.StorageConstraints{
   368  		"data": []params.StorageConstraints{
   369  			params.StorageConstraints{},
   370  		},
   371  	}
   372  
   373  	ctx := context.HookContext{}
   374  	addStorageToContext(&ctx, "data", params.StorageConstraints{})
   375  	assertStorageAddInContext(c, ctx, expected)
   376  }
   377  
   378  var two = uint64(2)
   379  
   380  func (s *InterfaceSuite) TestStorageAddConstraintsSameStorage(c *gc.C) {
   381  	expected := map[string][]params.StorageConstraints{
   382  		"data": []params.StorageConstraints{
   383  			params.StorageConstraints{},
   384  			params.StorageConstraints{Count: &two},
   385  		},
   386  	}
   387  
   388  	ctx := context.HookContext{}
   389  	addStorageToContext(&ctx, "data", params.StorageConstraints{})
   390  	addStorageToContext(&ctx, "data", params.StorageConstraints{Count: &two})
   391  	assertStorageAddInContext(c, ctx, expected)
   392  }
   393  
   394  func (s *InterfaceSuite) TestStorageAddConstraintsDifferentStorage(c *gc.C) {
   395  	expected := map[string][]params.StorageConstraints{
   396  		"data": []params.StorageConstraints{params.StorageConstraints{}},
   397  		"diff": []params.StorageConstraints{
   398  			params.StorageConstraints{Count: &two}},
   399  	}
   400  
   401  	ctx := context.HookContext{}
   402  	addStorageToContext(&ctx, "data", params.StorageConstraints{})
   403  	addStorageToContext(&ctx, "diff", params.StorageConstraints{Count: &two})
   404  	assertStorageAddInContext(c, ctx, expected)
   405  }
   406  
   407  func addStorageToContext(ctx *context.HookContext,
   408  	name string,
   409  	cons params.StorageConstraints,
   410  ) {
   411  	addOne := map[string]params.StorageConstraints{name: cons}
   412  	ctx.AddUnitStorage(addOne)
   413  }
   414  
   415  func assertStorageAddInContext(c *gc.C,
   416  	ctx context.HookContext, expected map[string][]params.StorageConstraints,
   417  ) {
   418  	obtained := context.StorageAddConstraints(&ctx)
   419  	c.Assert(len(obtained), gc.Equals, len(expected))
   420  	for k, v := range obtained {
   421  		c.Assert(v, jc.SameContents, expected[k])
   422  	}
   423  }
   424  
   425  type mockProcess struct {
   426  	kill func() error
   427  }
   428  
   429  func (p *mockProcess) Kill() error {
   430  	return p.kill()
   431  }
   432  
   433  func (p *mockProcess) Pid() int {
   434  	return 123
   435  }