1
// Copyright 2013 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
13
gc "launchpad.net/gocheck"
15
"launchpad.net/juju-core/instance"
16
"launchpad.net/juju-core/state"
17
"launchpad.net/juju-core/state/api/params"
18
"launchpad.net/juju-core/state/apiserver/client"
19
"launchpad.net/juju-core/testing"
20
jc "launchpad.net/juju-core/testing/checkers"
21
"launchpad.net/juju-core/utils/exec"
22
"launchpad.net/juju-core/utils/ssh"
25
type runSuite struct {
29
var _ = gc.Suite(&runSuite{})
31
func (s *runSuite) addMachine(c *gc.C) *state.Machine {
32
machine, err := s.State.AddMachine("quantal", state.JobHostUnits)
33
c.Assert(err, gc.IsNil)
37
func (s *runSuite) addMachineWithAddress(c *gc.C, address string) *state.Machine {
38
machine := s.addMachine(c)
39
machine.SetAddresses([]instance.Address{instance.NewAddress(address)})
43
func (s *runSuite) TestRemoteParamsForMachinePopulates(c *gc.C) {
44
machine := s.addMachine(c)
45
result := client.RemoteParamsForMachine(machine, "command", time.Minute)
46
c.Assert(result.Command, gc.Equals, "command")
47
c.Assert(result.Timeout, gc.Equals, time.Minute)
48
c.Assert(result.MachineId, gc.Equals, machine.Id())
49
// Now an empty host isn't particularly useful, but the machine doesn't
50
// have an address to use.
51
c.Assert(machine.Addresses(), gc.HasLen, 0)
52
c.Assert(result.Host, gc.Equals, "")
55
func (s *runSuite) TestRemoteParamsForMachinePopulatesWithAddress(c *gc.C) {
56
machine := s.addMachineWithAddress(c, "10.3.2.1")
58
result := client.RemoteParamsForMachine(machine, "command", time.Minute)
59
c.Assert(result.Command, gc.Equals, "command")
60
c.Assert(result.Timeout, gc.Equals, time.Minute)
61
c.Assert(result.MachineId, gc.Equals, machine.Id())
62
c.Assert(result.Host, gc.Equals, "ubuntu@10.3.2.1")
65
func (s *runSuite) addUnit(c *gc.C, service *state.Service) *state.Unit {
66
unit, err := service.AddUnit()
67
c.Assert(err, gc.IsNil)
68
err = unit.AssignToNewMachine()
69
c.Assert(err, gc.IsNil)
70
mId, err := unit.AssignedMachineId()
71
c.Assert(err, gc.IsNil)
72
machine, err := s.State.Machine(mId)
73
c.Assert(err, gc.IsNil)
74
machine.SetAddresses([]instance.Address{instance.NewAddress("10.3.2.1")})
78
func (s *runSuite) TestGetAllUnitNames(c *gc.C) {
79
charm := s.AddTestingCharm(c, "dummy")
80
magic, err := s.State.AddService("magic", "user-admin", charm)
84
notAssigned, err := s.State.AddService("not-assigned", "user-admin", charm)
85
c.Assert(err, gc.IsNil)
86
_, err = notAssigned.AddUnit()
87
c.Assert(err, gc.IsNil)
89
_, err = s.State.AddService("no-units", "user-admin", charm)
90
c.Assert(err, gc.IsNil)
92
for i, test := range []struct {
99
message: "no units, expected nil slice",
101
message: "asking for a unit that isn't there",
102
units: []string{"foo/0"},
103
error: `unit "foo/0" not found`,
105
message: "asking for a service that isn't there",
106
services: []string{"foo"},
107
error: `service "foo" not found`,
109
message: "service with no units is not really an error",
110
services: []string{"no-units"},
112
message: "A service with units not assigned is an error",
113
services: []string{"not-assigned"},
114
error: `unit "not-assigned/0" is not assigned to a machine`,
116
message: "A service with units",
117
services: []string{"magic"},
118
expected: []string{"magic/0", "magic/1"},
120
message: "Asking for just a unit",
121
units: []string{"magic/0"},
122
expected: []string{"magic/0"},
124
message: "Asking for a unit, and the service",
125
services: []string{"magic"},
126
units: []string{"magic/0"},
127
expected: []string{"magic/0", "magic/1"},
129
c.Logf("%v: %s", i, test.message)
130
result, err := client.GetAllUnitNames(s.State, test.units, test.services)
131
if test.error == "" {
132
c.Check(err, gc.IsNil)
134
for _, unit := range result {
135
units = append(units, unit.Name())
137
c.Check(units, jc.SameContents, test.expected)
139
c.Check(err, gc.ErrorMatches, test.error)
144
func (s *runSuite) mockSSH(c *gc.C, cmd string) {
146
fakessh := filepath.Join(testbin, "ssh")
147
newPath := testbin + ":" + os.Getenv("PATH")
148
s.PatchEnvironment("PATH", newPath)
149
err := ioutil.WriteFile(fakessh, []byte(cmd), 0755)
150
c.Assert(err, gc.IsNil)
153
func (s *runSuite) TestParallelExecuteErrorsOnBlankHost(c *gc.C) {
154
s.mockSSH(c, echoInputShowArgs)
156
params := []*client.RemoteExec{
158
ExecParams: ssh.ExecParams{
160
Timeout: testing.LongWait,
165
runResults := client.ParallelExecute("/some/dir", params)
166
c.Assert(runResults.Results, gc.HasLen, 1)
167
result := runResults.Results[0]
168
c.Assert(result.Error, gc.Equals, "missing host address")
171
func (s *runSuite) TestParallelExecuteAddsIdentity(c *gc.C) {
172
s.mockSSH(c, echoInputShowArgs)
174
params := []*client.RemoteExec{
176
ExecParams: ssh.ExecParams{
179
Timeout: testing.LongWait,
184
runResults := client.ParallelExecute("/some/dir", params)
185
c.Assert(runResults.Results, gc.HasLen, 1)
186
result := runResults.Results[0]
187
c.Assert(result.Error, gc.Equals, "")
188
c.Assert(string(result.Stderr), jc.Contains, "-i /some/dir/system-identity")
191
func (s *runSuite) TestParallelExecuteCopiesAcrossMachineAndUnit(c *gc.C) {
192
s.mockSSH(c, echoInputShowArgs)
194
params := []*client.RemoteExec{
196
ExecParams: ssh.ExecParams{
199
Timeout: testing.LongWait,
201
MachineId: "machine-id",
206
runResults := client.ParallelExecute("/some/dir", params)
207
c.Assert(runResults.Results, gc.HasLen, 1)
208
result := runResults.Results[0]
209
c.Assert(result.Error, gc.Equals, "")
210
c.Assert(result.MachineId, gc.Equals, "machine-id")
211
c.Assert(result.UnitId, gc.Equals, "unit-id")
214
func (s *runSuite) TestRunOnAllMachines(c *gc.C) {
215
// Make three machines.
216
s.addMachineWithAddress(c, "10.3.2.1")
217
s.addMachineWithAddress(c, "10.3.2.2")
218
s.addMachineWithAddress(c, "10.3.2.3")
220
s.mockSSH(c, echoInput)
222
// hmm... this seems to be going through the api client, and from there
223
// through to the apiserver implementation. Not ideal, but it is how the
224
// other client tests are written.
225
client := s.APIState.Client()
226
results, err := client.RunOnAllMachines("hostname", testing.LongWait)
227
c.Assert(err, gc.IsNil)
228
c.Assert(results, gc.HasLen, 3)
229
var expectedResults []params.RunResult
230
for i := 0; i < 3; i++ {
231
expectedResults = append(expectedResults,
233
ExecResponse: exec.ExecResponse{Stdout: []byte("juju-run --no-context 'hostname'\n")},
234
MachineId: fmt.Sprint(i),
238
c.Assert(results, jc.DeepEquals, expectedResults)
241
func (s *runSuite) TestRunMachineAndService(c *gc.C) {
242
// Make three machines.
243
s.addMachineWithAddress(c, "10.3.2.1")
245
charm := s.AddTestingCharm(c, "dummy")
246
magic, err := s.State.AddService("magic", "user-admin", charm)
250
s.mockSSH(c, echoInput)
252
// hmm... this seems to be going through the api client, and from there
253
// through to the apiserver implementation. Not ideal, but it is how the
254
// other client tests are written.
255
client := s.APIState.Client()
256
results, err := client.Run(
258
Commands: "hostname",
259
Timeout: testing.LongWait,
260
Machines: []string{"0"},
261
Services: []string{"magic"},
263
c.Assert(err, gc.IsNil)
264
c.Assert(results, gc.HasLen, 3)
265
expectedResults := []params.RunResult{
267
ExecResponse: exec.ExecResponse{Stdout: []byte("[ -f \"$HOME/.juju-proxy\" ] && . \"$HOME/.juju-proxy\"\njuju-run --no-context 'hostname'\n")},
271
ExecResponse: exec.ExecResponse{Stdout: []byte("juju-run magic/0 'hostname'\n")},
276
ExecResponse: exec.ExecResponse{Stdout: []byte("juju-run magic/1 'hostname'\n")},
282
c.Assert(results, jc.DeepEquals, expectedResults)
285
var echoInputShowArgs = `#!/bin/bash
286
# Write the args to stderr
288
# And echo stdin to stdout
294
var echoInput = `#!/bin/bash
295
# And echo stdin to stdout