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