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