github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cmd/jujud/run_test.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package main 5 6 import ( 7 "fmt" 8 "os" 9 "path/filepath" 10 "runtime" 11 "strings" 12 "sync" 13 "time" 14 15 "github.com/juju/cmd" 16 "github.com/juju/cmd/cmdtesting" 17 "github.com/juju/loggo" 18 jujuos "github.com/juju/os" 19 jc "github.com/juju/testing/checkers" 20 "github.com/juju/utils" 21 "github.com/juju/utils/exec" 22 gc "gopkg.in/check.v1" 23 "gopkg.in/juju/names.v2" 24 25 cmdutil "github.com/juju/juju/cmd/jujud/util" 26 "github.com/juju/juju/core/machinelock" 27 "github.com/juju/juju/testing" 28 "github.com/juju/juju/worker/uniter" 29 ) 30 31 type RunTestSuite struct { 32 testing.BaseSuite 33 34 machinelock *fakemachinelock 35 } 36 37 func (s *RunTestSuite) SetUpTest(c *gc.C) { 38 s.BaseSuite.SetUpTest(c) 39 s.PatchValue(&cmdutil.DataDir, c.MkDir()) 40 s.machinelock = &fakemachinelock{} 41 } 42 43 var _ = gc.Suite(&RunTestSuite{}) 44 45 func (*RunTestSuite) TestArgParsing(c *gc.C) { 46 for i, test := range []struct { 47 title string 48 args []string 49 errMatch string 50 unit names.UnitTag 51 commands string 52 avoidContext bool 53 relationId string 54 remoteUnit string 55 forceRemoteUnit bool 56 }{{ 57 title: "no args", 58 errMatch: "missing unit-name", 59 }, { 60 title: "one arg", 61 args: []string{"foo"}, 62 errMatch: `"foo" is not a valid tag`, 63 }, { 64 title: "one arg", 65 args: []string{"foo/2"}, 66 errMatch: "missing commands", 67 }, { 68 title: "more than two arg", 69 args: []string{"foo/2", "bar", "baz"}, 70 errMatch: `unrecognized args: \["baz"\]`, 71 }, { 72 title: "unit and command assignment", 73 args: []string{"unit-name-2", "command"}, 74 unit: names.NewUnitTag("name/2"), 75 commands: "command", 76 relationId: "", 77 }, { 78 title: "unit id converted to tag", 79 args: []string{"foo/1", "command"}, 80 unit: names.NewUnitTag("foo/1"), 81 commands: "command", 82 relationId: "", 83 }, { 84 title: "execute not in a context", 85 args: []string{"--no-context", "command"}, 86 commands: "command", 87 avoidContext: true, 88 relationId: "", 89 forceRemoteUnit: false, 90 }, { 91 title: "relation-id", 92 args: []string{"--relation", "db:1", "unit-name-2", "command"}, 93 commands: "command", 94 unit: names.NewUnitTag("name/2"), 95 relationId: "db:1", 96 remoteUnit: "", 97 avoidContext: false, 98 forceRemoteUnit: false, 99 }, { 100 title: "remote-unit", 101 args: []string{"--remote-unit", "unit-name-1", "unit-name-2", "command"}, 102 commands: "command", 103 unit: names.NewUnitTag("name/2"), 104 avoidContext: false, 105 relationId: "", 106 remoteUnit: "unit-name-1", 107 forceRemoteUnit: false, 108 }, { 109 title: "no-remote-unit", 110 args: []string{"--force-remote-unit", "--relation", "mongodb:1", "unit-name-2", "command"}, 111 commands: "command", 112 unit: names.NewUnitTag("name/2"), 113 relationId: "mongodb:1", 114 forceRemoteUnit: true, 115 }, 116 } { 117 c.Logf("%d: %s", i, test.title) 118 runCommand := &RunCommand{} 119 err := cmdtesting.InitCommand(runCommand, test.args) 120 if test.errMatch == "" { 121 c.Assert(err, jc.ErrorIsNil) 122 c.Assert(runCommand.unit, gc.Equals, test.unit) 123 c.Assert(runCommand.commands, gc.Equals, test.commands) 124 c.Assert(runCommand.noContext, gc.Equals, test.avoidContext) 125 c.Assert(runCommand.relationId, gc.Equals, test.relationId) 126 c.Assert(runCommand.remoteUnitName, gc.Equals, test.remoteUnit) 127 c.Assert(runCommand.forceRemoteUnit, gc.Equals, test.forceRemoteUnit) 128 } else { 129 c.Assert(err, gc.ErrorMatches, test.errMatch) 130 } 131 } 132 } 133 134 func (s *RunTestSuite) runCommand() *RunCommand { 135 return &RunCommand{ 136 MachineLock: s.machinelock, 137 } 138 } 139 140 func (s *RunTestSuite) TestInsideContext(c *gc.C) { 141 s.PatchEnvironment("JUJU_CONTEXT_ID", "fake-id") 142 runCommand := s.runCommand() 143 err := runCommand.Init([]string{"foo", "bar"}) 144 c.Assert(err, gc.ErrorMatches, "juju-run cannot be called from within a hook.*") 145 } 146 147 func (s *RunTestSuite) TestMissingAgentName(c *gc.C) { 148 _, err := cmdtesting.RunCommand(c, s.runCommand(), "foo/2", "bar") 149 c.Assert(err, gc.ErrorMatches, `unit "foo/2" not found on this machine`) 150 } 151 152 func (s *RunTestSuite) TestMissingAgentTag(c *gc.C) { 153 _, err := cmdtesting.RunCommand(c, s.runCommand(), "unit-foo-2", "bar") 154 c.Assert(err, gc.ErrorMatches, `unit "foo/2" not found on this machine`) 155 } 156 157 func waitForResult(running <-chan *cmd.Context, timeout time.Duration) (*cmd.Context, error) { 158 select { 159 case result := <-running: 160 return result, nil 161 case <-time.After(timeout): 162 return nil, fmt.Errorf("timeout") 163 } 164 } 165 166 func (s *RunTestSuite) startRunAsync(c *gc.C, params []string) <-chan *cmd.Context { 167 resultChannel := make(chan *cmd.Context) 168 go func() { 169 ctx, err := cmdtesting.RunCommand(c, s.runCommand(), params...) 170 c.Assert(err, jc.Satisfies, cmd.IsRcPassthroughError) 171 c.Assert(err, gc.ErrorMatches, "subprocess encountered error code 0") 172 resultChannel <- ctx 173 close(resultChannel) 174 }() 175 return resultChannel 176 } 177 178 func (s *RunTestSuite) TestNoContext(c *gc.C) { 179 ctx, err := cmdtesting.RunCommand(c, s.runCommand(), "--no-context", "echo done") 180 c.Assert(err, jc.Satisfies, cmd.IsRcPassthroughError) 181 c.Assert(err, gc.ErrorMatches, "subprocess encountered error code 0") 182 c.Assert(strings.TrimRight(cmdtesting.Stdout(ctx), "\r\n"), gc.Equals, "done") 183 } 184 185 func (s *RunTestSuite) TestNoContextAsync(c *gc.C) { 186 channel := s.startRunAsync(c, []string{"--no-context", "echo done"}) 187 ctx, err := waitForResult(channel, testing.LongWait) 188 c.Assert(err, jc.ErrorIsNil) 189 c.Assert(strings.TrimRight(cmdtesting.Stdout(ctx), "\r\n"), gc.Equals, "done") 190 } 191 192 func (s *RunTestSuite) TestNoContextWithLock(c *gc.C) { 193 releaser, err := s.machinelock.Acquire(machinelock.Spec{}) 194 c.Assert(err, jc.ErrorIsNil) 195 196 channel := s.startRunAsync(c, []string{"--no-context", "echo done"}) 197 ctx, err := waitForResult(channel, testing.ShortWait) 198 c.Assert(err, gc.ErrorMatches, "timeout") 199 200 releaser() 201 202 ctx, err = waitForResult(channel, testing.LongWait) 203 c.Assert(err, jc.ErrorIsNil) 204 c.Assert(strings.TrimRight(cmdtesting.Stdout(ctx), "\r\n"), gc.Equals, "done") 205 } 206 207 func (s *RunTestSuite) TestMissingSocket(c *gc.C) { 208 if runtime.GOOS == "windows" { 209 c.Skip("Current implementation of named pipes loops if the socket is missing") 210 } 211 agentDir := filepath.Join(cmdutil.DataDir, "agents", "unit-foo-1") 212 err := os.MkdirAll(agentDir, 0755) 213 c.Assert(err, jc.ErrorIsNil) 214 215 _, err = cmdtesting.RunCommand(c, s.runCommand(), "foo/1", "bar") 216 c.Assert(err, gc.ErrorMatches, `.*dial unix .*/run.socket:.*`+utils.NoSuchFileErrRegexp) 217 } 218 219 func (s *RunTestSuite) TestRunning(c *gc.C) { 220 loggo.GetLogger("worker.uniter").SetLogLevel(loggo.TRACE) 221 s.runListenerForAgent(c, "unit-foo-1") 222 223 ctx, err := cmdtesting.RunCommand(c, s.runCommand(), "foo/1", "bar") 224 c.Check(cmd.IsRcPassthroughError(err), jc.IsTrue) 225 c.Assert(err, gc.ErrorMatches, "subprocess encountered error code 42") 226 c.Assert(cmdtesting.Stdout(ctx), gc.Equals, "bar stdout") 227 c.Assert(cmdtesting.Stderr(ctx), gc.Equals, "bar stderr") 228 } 229 230 func (s *RunTestSuite) TestRunningRelation(c *gc.C) { 231 loggo.GetLogger("worker.uniter").SetLogLevel(loggo.TRACE) 232 s.runListenerForAgent(c, "unit-foo-1") 233 234 ctx, err := cmdtesting.RunCommand(c, s.runCommand(), "--relation", "db:1", "foo/1", "bar") 235 c.Check(cmd.IsRcPassthroughError(err), jc.IsTrue) 236 c.Assert(err, gc.ErrorMatches, "subprocess encountered error code 42") 237 c.Assert(cmdtesting.Stdout(ctx), gc.Equals, "bar stdout") 238 c.Assert(cmdtesting.Stderr(ctx), gc.Equals, "bar stderr") 239 } 240 241 func (s *RunTestSuite) TestRunningBadRelation(c *gc.C) { 242 loggo.GetLogger("worker.uniter").SetLogLevel(loggo.TRACE) 243 s.runListenerForAgent(c, "unit-foo-1") 244 245 _, err := cmdtesting.RunCommand(c, s.runCommand(), "--relation", "badrelation:W", "foo/1", "bar") 246 c.Check(cmd.IsRcPassthroughError(err), jc.IsFalse) 247 c.Assert(err, gc.ErrorMatches, "invalid relation id") 248 } 249 250 func (s *RunTestSuite) TestRunningRemoteUnitNoRelation(c *gc.C) { 251 loggo.GetLogger("worker.uniter").SetLogLevel(loggo.TRACE) 252 s.runListenerForAgent(c, "unit-foo-1") 253 254 _, err := cmdtesting.RunCommand(c, s.runCommand(), "--remote-unit", "remote/0", "foo/1", "bar") 255 c.Check(cmd.IsRcPassthroughError(err), jc.IsFalse) 256 c.Assert(err, gc.ErrorMatches, "remote unit: remote/0, provided without a relation") 257 } 258 259 func (s *RunTestSuite) TestSkipCheckAndRemoteUnit(c *gc.C) { 260 loggo.GetLogger("worker.uniter").SetLogLevel(loggo.TRACE) 261 s.runListenerForAgent(c, "unit-foo-1") 262 263 ctx, err := cmdtesting.RunCommand(c, s.runCommand(), "--force-remote-unit", "--remote-unit", "unit-name-2", "--relation", "db:1", "foo/1", "bar") 264 c.Check(cmd.IsRcPassthroughError(err), jc.IsTrue) 265 c.Assert(err, gc.ErrorMatches, "subprocess encountered error code 42") 266 c.Assert(cmdtesting.Stdout(ctx), gc.Equals, "bar stdout") 267 c.Assert(cmdtesting.Stderr(ctx), gc.Equals, "bar stderr") 268 } 269 270 func (s *RunTestSuite) TestCheckRelationIdValid(c *gc.C) { 271 for i, test := range []struct { 272 title string 273 input string 274 output int 275 err bool 276 }{ 277 { 278 title: "valid, id only", 279 input: "0", 280 output: 0, 281 err: false, 282 }, { 283 title: "valid, relation:id", 284 input: "db:1", 285 output: 1, 286 err: false, 287 }, { 288 title: "not valid, just relation", 289 input: "db", 290 output: -1, 291 err: true, 292 }, { 293 title: "not valud, malformed relation:id", 294 input: "db:X", 295 output: -1, 296 err: true, 297 }, 298 } { 299 c.Logf("%d: %s", i, test.title) 300 relationId, err := checkRelationId(test.input) 301 c.Assert(relationId, gc.Equals, test.output) 302 if test.err { 303 c.Assert(err, gc.NotNil) 304 } 305 } 306 } 307 308 func (s *RunTestSuite) runListenerForAgent(c *gc.C, agent string) { 309 agentDir := filepath.Join(cmdutil.DataDir, "agents", agent) 310 err := os.MkdirAll(agentDir, 0755) 311 c.Assert(err, jc.ErrorIsNil) 312 var socketPath string 313 switch jujuos.HostOS() { 314 case jujuos.Windows: 315 socketPath = fmt.Sprintf(`\\.\pipe\%s-run`, agent) 316 default: 317 socketPath = fmt.Sprintf("%s/run.socket", agentDir) 318 } 319 listener, err := uniter.NewRunListener(uniter.RunListenerConfig{ 320 SocketPath: socketPath, 321 CommandRunner: &mockRunner{c}, 322 }) 323 c.Assert(err, jc.ErrorIsNil) 324 c.Assert(listener, gc.NotNil) 325 s.AddCleanup(func(*gc.C) { 326 c.Assert(listener.Close(), jc.ErrorIsNil) 327 }) 328 } 329 330 type mockRunner struct { 331 c *gc.C 332 } 333 334 var _ uniter.CommandRunner = (*mockRunner)(nil) 335 336 func (r *mockRunner) RunCommands(args uniter.RunCommandsArgs) (results *exec.ExecResponse, err error) { 337 r.c.Log("mock runner: " + args.Commands) 338 return &exec.ExecResponse{ 339 Code: 42, 340 Stdout: []byte(args.Commands + " stdout"), 341 Stderr: []byte(args.Commands + " stderr"), 342 }, nil 343 } 344 345 type fakemachinelock struct { 346 mu sync.Mutex 347 } 348 349 func (f *fakemachinelock) Acquire(spec machinelock.Spec) (func(), error) { 350 f.mu.Lock() 351 return func() { 352 f.mu.Unlock() 353 }, nil 354 } 355 356 func (f *fakemachinelock) Report(opts ...machinelock.ReportOption) (string, error) { 357 return "", nil 358 }