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