github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/worker/uniter/runner/context_test.go (about)

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