github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/cmd/juju/commands/ssh_test.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package commands 5 6 import ( 7 "bytes" 8 "fmt" 9 "os" 10 "path/filepath" 11 "reflect" 12 "strings" 13 14 "github.com/juju/cmd" 15 jc "github.com/juju/testing/checkers" 16 gc "gopkg.in/check.v1" 17 "gopkg.in/juju/charm.v5" 18 19 "github.com/juju/juju/apiserver" 20 "github.com/juju/juju/cmd/envcmd" 21 "github.com/juju/juju/juju/testing" 22 "github.com/juju/juju/network" 23 "github.com/juju/juju/state" 24 "github.com/juju/juju/testcharms" 25 coretesting "github.com/juju/juju/testing" 26 "github.com/juju/juju/utils/ssh" 27 ) 28 29 var _ = gc.Suite(&SSHSuite{}) 30 31 type SSHSuite struct { 32 SSHCommonSuite 33 } 34 35 type SSHCommonSuite struct { 36 testing.JujuConnSuite 37 bin string 38 } 39 40 func (s *SSHCommonSuite) SetUpTest(c *gc.C) { 41 s.JujuConnSuite.SetUpTest(c) 42 s.PatchValue(&getJujuExecutable, func() (string, error) { return "juju", nil }) 43 44 s.bin = c.MkDir() 45 s.PatchEnvPathPrepend(s.bin) 46 for _, name := range patchedCommands { 47 f, err := os.OpenFile(filepath.Join(s.bin, name), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0777) 48 c.Assert(err, jc.ErrorIsNil) 49 _, err = f.Write([]byte(fakecommand)) 50 c.Assert(err, jc.ErrorIsNil) 51 err = f.Close() 52 c.Assert(err, jc.ErrorIsNil) 53 } 54 client, _ := ssh.NewOpenSSHClient() 55 s.PatchValue(&ssh.DefaultClient, client) 56 } 57 58 const ( 59 noProxy = `-o StrictHostKeyChecking no -o PasswordAuthentication no -o ServerAliveInterval 30 ` 60 args = `-o StrictHostKeyChecking no -o ProxyCommand juju ssh --proxy=false --pty=false localhost nc %h %p -o PasswordAuthentication no -o ServerAliveInterval 30 ` 61 commonArgsNoProxy = noProxy + `-o UserKnownHostsFile /dev/null ` 62 commonArgs = args + `-o UserKnownHostsFile /dev/null ` 63 sshArgs = args + `-t -t -o UserKnownHostsFile /dev/null ` 64 sshArgsNoProxy = noProxy + `-t -t -o UserKnownHostsFile /dev/null ` 65 ) 66 67 var sshTests = []struct { 68 about string 69 args []string 70 result string 71 }{ 72 { 73 "connect to machine 0", 74 []string{"ssh", "0"}, 75 sshArgs + "ubuntu@dummyenv-0.internal", 76 }, 77 { 78 "connect to machine 0 and pass extra arguments", 79 []string{"ssh", "0", "uname", "-a"}, 80 sshArgs + "ubuntu@dummyenv-0.internal uname -a", 81 }, 82 { 83 "connect to unit mysql/0", 84 []string{"ssh", "mysql/0"}, 85 sshArgs + "ubuntu@dummyenv-0.internal", 86 }, 87 { 88 "connect to unit mongodb/1 as the mongo user", 89 []string{"ssh", "mongo@mongodb/1"}, 90 sshArgs + "mongo@dummyenv-2.internal", 91 }, 92 { 93 "connect to unit mongodb/1 and pass extra arguments", 94 []string{"ssh", "mongodb/1", "ls", "/"}, 95 sshArgs + "ubuntu@dummyenv-2.internal ls /", 96 }, 97 { 98 "connect to unit mysql/0 without proxy", 99 []string{"ssh", "--proxy=false", "mysql/0"}, 100 sshArgsNoProxy + "ubuntu@dummyenv-0.dns", 101 }, 102 } 103 104 func (s *SSHSuite) TestSSHCommand(c *gc.C) { 105 m := s.makeMachines(3, c, true) 106 ch := testcharms.Repo.CharmDir("dummy") 107 curl := charm.MustParseURL( 108 fmt.Sprintf("local:quantal/%s-%d", ch.Meta().Name, ch.Revision()), 109 ) 110 dummy, err := s.State.AddCharm(ch, curl, "dummy-path", "dummy-1-sha256") 111 c.Assert(err, jc.ErrorIsNil) 112 srv := s.AddTestingService(c, "mysql", dummy) 113 s.addUnit(srv, m[0], c) 114 115 srv = s.AddTestingService(c, "mongodb", dummy) 116 s.addUnit(srv, m[1], c) 117 s.addUnit(srv, m[2], c) 118 119 for i, t := range sshTests { 120 c.Logf("test %d: %s -> %s", i, t.about, t.args) 121 ctx := coretesting.Context(c) 122 jujucmd := cmd.NewSuperCommand(cmd.SuperCommandParams{}) 123 jujucmd.Register(envcmd.Wrap(&SSHCommand{})) 124 125 code := cmd.Main(jujucmd, ctx, t.args) 126 c.Check(code, gc.Equals, 0) 127 c.Check(ctx.Stderr.(*bytes.Buffer).String(), gc.Equals, "") 128 c.Check(strings.TrimRight(ctx.Stdout.(*bytes.Buffer).String(), "\r\n"), gc.Equals, t.result) 129 } 130 } 131 132 func (s *SSHSuite) TestSSHCommandEnvironProxySSH(c *gc.C) { 133 s.makeMachines(1, c, true) 134 // Setting proxy-ssh=false in the environment overrides --proxy. 135 err := s.State.UpdateEnvironConfig(map[string]interface{}{"proxy-ssh": false}, nil, nil) 136 c.Assert(err, jc.ErrorIsNil) 137 ctx := coretesting.Context(c) 138 jujucmd := cmd.NewSuperCommand(cmd.SuperCommandParams{}) 139 jujucmd.Register(envcmd.Wrap(&SSHCommand{})) 140 code := cmd.Main(jujucmd, ctx, []string{"ssh", "0"}) 141 c.Check(code, gc.Equals, 0) 142 c.Check(ctx.Stderr.(*bytes.Buffer).String(), gc.Equals, "") 143 c.Check(strings.TrimRight(ctx.Stdout.(*bytes.Buffer).String(), "\r\n"), gc.Equals, sshArgsNoProxy+"ubuntu@dummyenv-0.dns") 144 } 145 146 func (s *SSHSuite) TestSSHWillWorkInUpgrade(c *gc.C) { 147 // Check the API client interface used by "juju ssh" against what 148 // the API server will allow during upgrades. Ensure that the API 149 // server will allow all required API calls to support SSH. 150 type concrete struct { 151 sshAPIClient 152 } 153 t := reflect.TypeOf(concrete{}) 154 for i := 0; i < t.NumMethod(); i++ { 155 name := t.Method(i).Name 156 157 // Close isn't an API method and ServiceCharmRelations is not 158 // relevant to "juju ssh". 159 if name == "Close" || name == "ServiceCharmRelations" { 160 continue 161 } 162 c.Logf("checking %q", name) 163 c.Check(apiserver.IsMethodAllowedDuringUpgrade("Client", name), jc.IsTrue) 164 } 165 } 166 167 type callbackAttemptStarter struct { 168 next func() bool 169 } 170 171 func (s *callbackAttemptStarter) Start() attempt { 172 return callbackAttempt{next: s.next} 173 } 174 175 type callbackAttempt struct { 176 next func() bool 177 } 178 179 func (a callbackAttempt) Next() bool { 180 return a.next() 181 } 182 183 func (s *SSHSuite) TestSSHCommandHostAddressRetry(c *gc.C) { 184 s.testSSHCommandHostAddressRetry(c, false) 185 } 186 187 func (s *SSHSuite) TestSSHCommandHostAddressRetryProxy(c *gc.C) { 188 s.testSSHCommandHostAddressRetry(c, true) 189 } 190 191 func (s *SSHSuite) testSSHCommandHostAddressRetry(c *gc.C, proxy bool) { 192 m := s.makeMachines(1, c, false) 193 ctx := coretesting.Context(c) 194 195 var called int 196 next := func() bool { 197 called++ 198 return called < 2 199 } 200 attemptStarter := &callbackAttemptStarter{next: next} 201 s.PatchValue(&sshHostFromTargetAttemptStrategy, attemptStarter) 202 203 // Ensure that the ssh command waits for a public address, or the attempt 204 // strategy's Done method returns false. 205 args := []string{"--proxy=" + fmt.Sprint(proxy), "0"} 206 code := cmd.Main(envcmd.Wrap(&SSHCommand{}), ctx, args) 207 c.Check(code, gc.Equals, 1) 208 c.Assert(called, gc.Equals, 2) 209 called = 0 210 attemptStarter.next = func() bool { 211 called++ 212 if called > 1 { 213 s.setAddresses(m[0], c) 214 } 215 return true 216 } 217 code = cmd.Main(envcmd.Wrap(&SSHCommand{}), ctx, args) 218 c.Check(code, gc.Equals, 0) 219 c.Assert(called, gc.Equals, 2) 220 } 221 222 func (s *SSHCommonSuite) setAddresses(m *state.Machine, c *gc.C) { 223 addrPub := network.NewScopedAddress( 224 fmt.Sprintf("dummyenv-%s.dns", m.Id()), 225 network.ScopePublic, 226 ) 227 addrPriv := network.NewScopedAddress( 228 fmt.Sprintf("dummyenv-%s.internal", m.Id()), 229 network.ScopeCloudLocal, 230 ) 231 err := m.SetProviderAddresses(addrPub, addrPriv) 232 c.Assert(err, jc.ErrorIsNil) 233 } 234 235 func (s *SSHCommonSuite) makeMachines(n int, c *gc.C, setAddresses bool) []*state.Machine { 236 var machines = make([]*state.Machine, n) 237 for i := 0; i < n; i++ { 238 m, err := s.State.AddMachine("quantal", state.JobHostUnits) 239 c.Assert(err, jc.ErrorIsNil) 240 if setAddresses { 241 s.setAddresses(m, c) 242 } 243 // must set an instance id as the ssh command uses that as a signal the 244 // machine has been provisioned 245 inst, md := testing.AssertStartInstance(c, s.Environ, m.Id()) 246 c.Assert(m.SetProvisioned(inst.Id(), "fake_nonce", md), gc.IsNil) 247 machines[i] = m 248 } 249 return machines 250 } 251 252 func (s *SSHCommonSuite) addUnit(srv *state.Service, m *state.Machine, c *gc.C) { 253 u, err := srv.AddUnit() 254 c.Assert(err, jc.ErrorIsNil) 255 err = u.AssignToMachine(m) 256 c.Assert(err, jc.ErrorIsNil) 257 }