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