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  }