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 }