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