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  }