github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/worker/uniter/runner/factory_test.go (about)

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package runner_test
     5  
     6  import (
     7  	"strings"
     8  	"time"
     9  
    10  	"github.com/juju/charm/v12/hooks"
    11  	"github.com/juju/clock/testclock"
    12  	"github.com/juju/loggo"
    13  	"github.com/juju/names/v5"
    14  	jc "github.com/juju/testing/checkers"
    15  	"github.com/juju/utils/v3"
    16  	gc "gopkg.in/check.v1"
    17  
    18  	"github.com/juju/juju/api/agent/uniter"
    19  	"github.com/juju/juju/state"
    20  	"github.com/juju/juju/worker/common/charmrunner"
    21  	"github.com/juju/juju/worker/uniter/hook"
    22  	"github.com/juju/juju/worker/uniter/runner"
    23  	"github.com/juju/juju/worker/uniter/runner/context"
    24  	runnertesting "github.com/juju/juju/worker/uniter/runner/testing"
    25  )
    26  
    27  type FactorySuite struct {
    28  	ContextSuite
    29  }
    30  
    31  var _ = gc.Suite(&FactorySuite{})
    32  
    33  func (s *FactorySuite) AssertPaths(c *gc.C, rnr runner.Runner) {
    34  	c.Assert(runner.RunnerPaths(rnr), gc.DeepEquals, s.paths)
    35  }
    36  
    37  func (s *FactorySuite) TestNewCommandRunnerNoRelation(c *gc.C) {
    38  	rnr, err := s.factory.NewCommandRunner(context.CommandInfo{RelationId: -1})
    39  	c.Assert(err, jc.ErrorIsNil)
    40  	s.AssertPaths(c, rnr)
    41  }
    42  
    43  func (s *FactorySuite) TestNewCommandRunnerRelationIdDoesNotExist(c *gc.C) {
    44  	for _, value := range []bool{true, false} {
    45  		_, err := s.factory.NewCommandRunner(context.CommandInfo{
    46  			RelationId: 12, ForceRemoteUnit: value,
    47  		})
    48  		c.Check(err, gc.ErrorMatches, `unknown relation id: 12`)
    49  	}
    50  }
    51  
    52  func (s *FactorySuite) TestNewCommandRunnerRemoteUnitInvalid(c *gc.C) {
    53  	for _, value := range []bool{true, false} {
    54  		_, err := s.factory.NewCommandRunner(context.CommandInfo{
    55  			RelationId: 0, RemoteUnitName: "blah", ForceRemoteUnit: value,
    56  		})
    57  		c.Check(err, gc.ErrorMatches, `invalid remote unit: blah`)
    58  	}
    59  }
    60  
    61  func (s *FactorySuite) TestNewCommandRunnerRemoteUnitInappropriate(c *gc.C) {
    62  	for _, value := range []bool{true, false} {
    63  		_, err := s.factory.NewCommandRunner(context.CommandInfo{
    64  			RelationId: -1, RemoteUnitName: "blah/123", ForceRemoteUnit: value,
    65  		})
    66  		c.Check(err, gc.ErrorMatches, `remote unit provided without a relation: blah/123`)
    67  	}
    68  }
    69  
    70  func (s *FactorySuite) TestNewCommandRunnerEmptyRelation(c *gc.C) {
    71  	_, err := s.factory.NewCommandRunner(context.CommandInfo{RelationId: 1})
    72  	c.Check(err, gc.ErrorMatches, `cannot infer remote unit in empty relation 1`)
    73  }
    74  
    75  func (s *FactorySuite) TestNewCommandRunnerRemoteUnitAmbiguous(c *gc.C) {
    76  	s.membership[1] = []string{"foo/0", "foo/1"}
    77  	_, err := s.factory.NewCommandRunner(context.CommandInfo{RelationId: 1})
    78  	c.Check(err, gc.ErrorMatches, `ambiguous remote unit; possibilities are \[foo/0 foo/1\]`)
    79  }
    80  
    81  func (s *FactorySuite) TestNewCommandRunnerRemoteUnitMissing(c *gc.C) {
    82  	s.membership[0] = []string{"foo/0", "foo/1"}
    83  	_, err := s.factory.NewCommandRunner(context.CommandInfo{
    84  		RelationId: 0, RemoteUnitName: "blah/123",
    85  	})
    86  	c.Check(err, gc.ErrorMatches, `unknown remote unit blah/123; possibilities are \[foo/0 foo/1\]`)
    87  }
    88  
    89  func (s *FactorySuite) TestNewCommandRunnerForceNoRemoteUnit(c *gc.C) {
    90  	rnr, err := s.factory.NewCommandRunner(context.CommandInfo{
    91  		RelationId: 0, ForceRemoteUnit: true,
    92  	})
    93  	c.Assert(err, jc.ErrorIsNil)
    94  	s.AssertPaths(c, rnr)
    95  }
    96  
    97  func (s *FactorySuite) TestNewCommandRunnerForceRemoteUnitMissing(c *gc.C) {
    98  	_, err := s.factory.NewCommandRunner(context.CommandInfo{
    99  		RelationId: 0, RemoteUnitName: "blah/123", ForceRemoteUnit: true,
   100  	})
   101  	c.Assert(err, gc.IsNil)
   102  }
   103  
   104  func (s *FactorySuite) TestNewCommandRunnerInferRemoteUnit(c *gc.C) {
   105  	s.membership[0] = []string{"foo/2"}
   106  	rnr, err := s.factory.NewCommandRunner(context.CommandInfo{RelationId: 0})
   107  	c.Assert(err, jc.ErrorIsNil)
   108  	s.AssertPaths(c, rnr)
   109  }
   110  
   111  func (s *FactorySuite) TestNewHookRunner(c *gc.C) {
   112  	rnr, err := s.factory.NewHookRunner(hook.Info{Kind: hooks.ConfigChanged})
   113  	c.Assert(err, jc.ErrorIsNil)
   114  	s.AssertPaths(c, rnr)
   115  }
   116  
   117  func (s *FactorySuite) TestNewHookRunnerWithBadHook(c *gc.C) {
   118  	rnr, err := s.factory.NewHookRunner(hook.Info{})
   119  	c.Assert(rnr, gc.IsNil)
   120  	c.Assert(err, gc.ErrorMatches, `unknown hook kind ""`)
   121  }
   122  
   123  func (s *FactorySuite) TestNewHookRunnerWithStorage(c *gc.C) {
   124  	// We need to set up a unit that has storage metadata defined.
   125  	ch := s.AddTestingCharm(c, "storage-block")
   126  	sCons := map[string]state.StorageConstraints{
   127  		"data": {Pool: "", Size: 1024, Count: 1},
   128  	}
   129  	application := s.AddTestingApplicationWithStorage(c, "storage-block", ch, sCons)
   130  	s.machine = nil // allocate a new machine
   131  	unit := s.AddUnit(c, application)
   132  
   133  	sb, err := state.NewStorageBackend(s.State)
   134  	c.Assert(err, jc.ErrorIsNil)
   135  	storageAttachments, err := sb.UnitStorageAttachments(unit.UnitTag())
   136  	c.Assert(err, jc.ErrorIsNil)
   137  	c.Assert(storageAttachments, gc.HasLen, 1)
   138  	storageTag := storageAttachments[0].StorageInstance()
   139  
   140  	volume, err := sb.StorageInstanceVolume(storageTag)
   141  	c.Assert(err, jc.ErrorIsNil)
   142  	volumeTag := volume.VolumeTag()
   143  	machineTag := s.machine.MachineTag()
   144  
   145  	err = sb.SetVolumeInfo(
   146  		volumeTag, state.VolumeInfo{
   147  			VolumeId: "vol-123",
   148  			Size:     456,
   149  		},
   150  	)
   151  	c.Assert(err, jc.ErrorIsNil)
   152  	err = sb.SetVolumeAttachmentInfo(
   153  		machineTag, volumeTag, state.VolumeAttachmentInfo{
   154  			DeviceName: "sdb",
   155  		},
   156  	)
   157  	c.Assert(err, jc.ErrorIsNil)
   158  
   159  	password, err := utils.RandomPassword()
   160  	c.Assert(err, jc.ErrorIsNil)
   161  	err = unit.SetPassword(password)
   162  	c.Assert(err, jc.ErrorIsNil)
   163  	st := s.OpenAPIAs(c, unit.Tag(), password)
   164  	uniter, err := uniter.NewFromConnection(st)
   165  	c.Assert(err, jc.ErrorIsNil)
   166  	c.Assert(s.uniter, gc.NotNil)
   167  	apiUnit, err := uniter.Unit(unit.Tag().(names.UnitTag))
   168  	c.Assert(err, jc.ErrorIsNil)
   169  
   170  	contextFactory, err := context.NewContextFactory(context.FactoryConfig{
   171  		State:            uniter,
   172  		Unit:             apiUnit,
   173  		Tracker:          &runnertesting.FakeTracker{},
   174  		GetRelationInfos: s.getRelationInfos,
   175  		SecretsClient:    s.secrets,
   176  		Payloads:         s.payloads,
   177  		Paths:            s.paths,
   178  		Clock:            testclock.NewClock(time.Time{}),
   179  		Logger:           loggo.GetLogger("test"),
   180  	})
   181  	c.Assert(err, jc.ErrorIsNil)
   182  	factory, err := runner.NewFactory(
   183  		s.paths,
   184  		contextFactory,
   185  		runner.NewRunner,
   186  		nil,
   187  	)
   188  	c.Assert(err, jc.ErrorIsNil)
   189  
   190  	rnr, err := factory.NewHookRunner(hook.Info{
   191  		Kind:      hooks.StorageAttached,
   192  		StorageId: "data/0",
   193  	})
   194  	c.Assert(err, jc.ErrorIsNil)
   195  	s.AssertPaths(c, rnr)
   196  	ctx := rnr.Context()
   197  	c.Assert(ctx, gc.NotNil)
   198  	c.Assert(ctx.UnitName(), gc.Equals, "storage-block/0")
   199  }
   200  
   201  func (s *FactorySuite) TestNewHookRunnerWithRelation(c *gc.C) {
   202  	rnr, err := s.factory.NewHookRunner(hook.Info{
   203  		Kind:       hooks.RelationBroken,
   204  		RelationId: 1,
   205  	})
   206  	c.Assert(err, jc.ErrorIsNil)
   207  	s.AssertPaths(c, rnr)
   208  }
   209  
   210  func (s *FactorySuite) TestNewHookRunnerWithBadRelation(c *gc.C) {
   211  	rnr, err := s.factory.NewHookRunner(hook.Info{
   212  		Kind:       hooks.RelationBroken,
   213  		RelationId: 12345,
   214  	})
   215  	c.Assert(rnr, gc.IsNil)
   216  	c.Assert(err, gc.ErrorMatches, `unknown relation id: 12345`)
   217  }
   218  
   219  func (s *FactorySuite) TestNewActionRunnerGood(c *gc.C) {
   220  	s.SetCharm(c, "dummy")
   221  	for i, test := range []struct {
   222  		actionName string
   223  		payload    map[string]interface{}
   224  	}{
   225  		{
   226  			actionName: "snapshot",
   227  			payload: map[string]interface{}{
   228  				"outfile": "/some/file.bz2",
   229  			},
   230  		},
   231  		{
   232  			// juju-exec should work as a predefined action even if
   233  			// it's not part of the charm
   234  			actionName: "juju-exec",
   235  			payload: map[string]interface{}{
   236  				"command": "foo",
   237  				"timeout": 0.0,
   238  			},
   239  		},
   240  	} {
   241  		c.Logf("test %d", i)
   242  		operationID, err := s.model.EnqueueOperation("a test", 1)
   243  		c.Assert(err, jc.ErrorIsNil)
   244  		action, err := s.model.EnqueueAction(operationID, s.unit.Tag(), test.actionName, test.payload, true, "group", nil)
   245  		c.Assert(err, jc.ErrorIsNil)
   246  		uniterAction := uniter.NewAction(
   247  			action.Id(),
   248  			action.Name(),
   249  			action.Parameters(),
   250  			action.Parallel(),
   251  			action.ExecutionGroup(),
   252  		)
   253  		rnr, err := s.factory.NewActionRunner(uniterAction, nil)
   254  		c.Assert(err, jc.ErrorIsNil)
   255  		s.AssertPaths(c, rnr)
   256  		ctx := rnr.Context()
   257  		data, err := ctx.ActionData()
   258  		c.Assert(err, jc.ErrorIsNil)
   259  		c.Assert(data, jc.DeepEquals, &context.ActionData{
   260  			Name:       test.actionName,
   261  			Tag:        action.ActionTag(),
   262  			Params:     test.payload,
   263  			ResultsMap: map[string]interface{}{},
   264  		})
   265  		vars, err := ctx.HookVars(s.paths, false, context.NewRemoteEnvironmenter(
   266  			func() []string { return []string{} },
   267  			func(k string) string {
   268  				switch k {
   269  				case "PATH", "Path":
   270  					return "pathy"
   271  				}
   272  				return ""
   273  			},
   274  			func(k string) (string, bool) {
   275  				switch k {
   276  				case "PATH", "Path":
   277  					return "pathy", true
   278  				}
   279  				return "", false
   280  			},
   281  		))
   282  		c.Assert(err, jc.ErrorIsNil)
   283  		c.Assert(len(vars) > 0, jc.IsTrue, gc.Commentf("expected HookVars but found none"))
   284  		combined := strings.Join(vars, "|")
   285  		c.Assert(combined, gc.Matches, `(^|.*\|)JUJU_ACTION_NAME=`+test.actionName+`(\|.*|$)`)
   286  		c.Assert(combined, gc.Matches, `(^|.*\|)JUJU_ACTION_UUID=`+action.Id()+`(\|.*|$)`)
   287  		c.Assert(combined, gc.Matches, `(^|.*\|)JUJU_ACTION_TAG=`+action.Tag().String()+`(\|.*|$)`)
   288  	}
   289  }
   290  
   291  func (s *FactorySuite) TestNewActionRunnerBadName(c *gc.C) {
   292  	s.SetCharm(c, "dummy")
   293  	action := uniter.NewAction("666", "no-such-action", nil, false, "")
   294  	rnr, err := s.factory.NewActionRunner(action, nil)
   295  	c.Check(rnr, gc.IsNil)
   296  	c.Check(err, gc.ErrorMatches, "cannot run \"no-such-action\" action: not defined")
   297  	c.Check(err, jc.Satisfies, charmrunner.IsBadActionError)
   298  }
   299  
   300  func (s *FactorySuite) TestNewActionRunnerBadParams(c *gc.C) {
   301  	s.SetCharm(c, "dummy")
   302  	action := uniter.NewAction("666", "snapshot", map[string]interface{}{
   303  		"outfile": 123,
   304  	}, true, "group")
   305  	rnr, err := s.factory.NewActionRunner(action, nil)
   306  	c.Check(rnr, gc.IsNil)
   307  	c.Check(err, gc.ErrorMatches, "cannot run \"snapshot\" action: .*")
   308  	c.Check(err, jc.Satisfies, charmrunner.IsBadActionError)
   309  }
   310  
   311  func (s *FactorySuite) TestNewActionRunnerWithCancel(c *gc.C) {
   312  	s.SetCharm(c, "dummy")
   313  	actionName := "snapshot"
   314  	payload := map[string]interface{}{
   315  		"outfile": "/some/file.bz2",
   316  	}
   317  	cancel := make(chan struct{})
   318  	operationID, err := s.model.EnqueueOperation("a test", 1)
   319  	c.Assert(err, jc.ErrorIsNil)
   320  	action, err := s.model.EnqueueAction(operationID, s.unit.Tag(), actionName, payload, true, "group", nil)
   321  	c.Assert(err, jc.ErrorIsNil)
   322  	uniterAction := uniter.NewAction(
   323  		action.Id(),
   324  		action.Name(),
   325  		action.Parameters(),
   326  		action.Parallel(),
   327  		action.ExecutionGroup(),
   328  	)
   329  	rnr, err := s.factory.NewActionRunner(uniterAction, cancel)
   330  	c.Assert(err, jc.ErrorIsNil)
   331  	s.AssertPaths(c, rnr)
   332  	ctx := rnr.Context()
   333  	data, err := ctx.ActionData()
   334  	c.Assert(err, jc.ErrorIsNil)
   335  	c.Assert(data, jc.DeepEquals, &context.ActionData{
   336  		Name:       actionName,
   337  		Tag:        action.ActionTag(),
   338  		Params:     payload,
   339  		ResultsMap: map[string]interface{}{},
   340  		Cancel:     cancel,
   341  	})
   342  	vars, err := ctx.HookVars(s.paths, false, context.NewRemoteEnvironmenter(
   343  		func() []string { return []string{} },
   344  		func(k string) string {
   345  			switch k {
   346  			case "PATH", "Path":
   347  				return "pathy"
   348  			}
   349  			return ""
   350  		},
   351  		func(k string) (string, bool) {
   352  			switch k {
   353  			case "PATH", "Path":
   354  				return "pathy", true
   355  			}
   356  			return "", false
   357  		},
   358  	))
   359  	c.Assert(err, jc.ErrorIsNil)
   360  	c.Assert(len(vars) > 0, jc.IsTrue, gc.Commentf("expected HookVars but found none"))
   361  	combined := strings.Join(vars, "|")
   362  	c.Assert(combined, gc.Matches, `(^|.*\|)JUJU_ACTION_NAME=`+actionName+`(\|.*|$)`)
   363  	c.Assert(combined, gc.Matches, `(^|.*\|)JUJU_ACTION_UUID=`+action.Id()+`(\|.*|$)`)
   364  	c.Assert(combined, gc.Matches, `(^|.*\|)JUJU_ACTION_TAG=`+action.Tag().String()+`(\|.*|$)`)
   365  }