github.com/cloudbase/juju-core@v0.0.0-20140504232958-a7271ac7912f/state/apiserver/client/run_test.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package client_test
     5  
     6  import (
     7  	"fmt"
     8  	"io/ioutil"
     9  	"path/filepath"
    10  	"time"
    11  
    12  	gc "launchpad.net/gocheck"
    13  
    14  	"launchpad.net/juju-core/instance"
    15  	"launchpad.net/juju-core/state"
    16  	"launchpad.net/juju-core/state/api/params"
    17  	"launchpad.net/juju-core/state/apiserver/client"
    18  	"launchpad.net/juju-core/testing"
    19  	jc "launchpad.net/juju-core/testing/checkers"
    20  	"launchpad.net/juju-core/utils/exec"
    21  	"launchpad.net/juju-core/utils/ssh"
    22  )
    23  
    24  type runSuite struct {
    25  	baseSuite
    26  }
    27  
    28  var _ = gc.Suite(&runSuite{})
    29  
    30  func (s *runSuite) addMachine(c *gc.C) *state.Machine {
    31  	machine, err := s.State.AddMachine("quantal", state.JobHostUnits)
    32  	c.Assert(err, gc.IsNil)
    33  	return machine
    34  }
    35  
    36  func (s *runSuite) addMachineWithAddress(c *gc.C, address string) *state.Machine {
    37  	machine := s.addMachine(c)
    38  	machine.SetAddresses([]instance.Address{instance.NewAddress(address)})
    39  	return machine
    40  }
    41  
    42  func (s *runSuite) TestRemoteParamsForMachinePopulates(c *gc.C) {
    43  	machine := s.addMachine(c)
    44  	result := client.RemoteParamsForMachine(machine, "command", time.Minute)
    45  	c.Assert(result.Command, gc.Equals, "command")
    46  	c.Assert(result.Timeout, gc.Equals, time.Minute)
    47  	c.Assert(result.MachineId, gc.Equals, machine.Id())
    48  	// Now an empty host isn't particularly useful, but the machine doesn't
    49  	// have an address to use.
    50  	c.Assert(machine.Addresses(), gc.HasLen, 0)
    51  	c.Assert(result.Host, gc.Equals, "")
    52  }
    53  
    54  func (s *runSuite) TestRemoteParamsForMachinePopulatesWithAddress(c *gc.C) {
    55  	machine := s.addMachineWithAddress(c, "10.3.2.1")
    56  
    57  	result := client.RemoteParamsForMachine(machine, "command", time.Minute)
    58  	c.Assert(result.Command, gc.Equals, "command")
    59  	c.Assert(result.Timeout, gc.Equals, time.Minute)
    60  	c.Assert(result.MachineId, gc.Equals, machine.Id())
    61  	c.Assert(result.Host, gc.Equals, "ubuntu@10.3.2.1")
    62  }
    63  
    64  func (s *runSuite) addUnit(c *gc.C, service *state.Service) *state.Unit {
    65  	unit, err := service.AddUnit()
    66  	c.Assert(err, gc.IsNil)
    67  	err = unit.AssignToNewMachine()
    68  	c.Assert(err, gc.IsNil)
    69  	mId, err := unit.AssignedMachineId()
    70  	c.Assert(err, gc.IsNil)
    71  	machine, err := s.State.Machine(mId)
    72  	c.Assert(err, gc.IsNil)
    73  	machine.SetAddresses([]instance.Address{instance.NewAddress("10.3.2.1")})
    74  	return unit
    75  }
    76  
    77  func (s *runSuite) TestGetAllUnitNames(c *gc.C) {
    78  	charm := s.AddTestingCharm(c, "dummy")
    79  	magic, err := s.State.AddService("magic", "user-admin", charm)
    80  	s.addUnit(c, magic)
    81  	s.addUnit(c, magic)
    82  
    83  	notAssigned, err := s.State.AddService("not-assigned", "user-admin", charm)
    84  	c.Assert(err, gc.IsNil)
    85  	_, err = notAssigned.AddUnit()
    86  	c.Assert(err, gc.IsNil)
    87  
    88  	_, err = s.State.AddService("no-units", "user-admin", charm)
    89  	c.Assert(err, gc.IsNil)
    90  
    91  	for i, test := range []struct {
    92  		message  string
    93  		expected []string
    94  		units    []string
    95  		services []string
    96  		error    string
    97  	}{{
    98  		message: "no units, expected nil slice",
    99  	}, {
   100  		message: "asking for a unit that isn't there",
   101  		units:   []string{"foo/0"},
   102  		error:   `unit "foo/0" not found`,
   103  	}, {
   104  		message:  "asking for a service that isn't there",
   105  		services: []string{"foo"},
   106  		error:    `service "foo" not found`,
   107  	}, {
   108  		message:  "service with no units is not really an error",
   109  		services: []string{"no-units"},
   110  	}, {
   111  		message:  "A service with units not assigned is an error",
   112  		services: []string{"not-assigned"},
   113  		error:    `unit "not-assigned/0" is not assigned to a machine`,
   114  	}, {
   115  		message:  "A service with units",
   116  		services: []string{"magic"},
   117  		expected: []string{"magic/0", "magic/1"},
   118  	}, {
   119  		message:  "Asking for just a unit",
   120  		units:    []string{"magic/0"},
   121  		expected: []string{"magic/0"},
   122  	}, {
   123  		message:  "Asking for a unit, and the service",
   124  		services: []string{"magic"},
   125  		units:    []string{"magic/0"},
   126  		expected: []string{"magic/0", "magic/1"},
   127  	}} {
   128  		c.Logf("%v: %s", i, test.message)
   129  		result, err := client.GetAllUnitNames(s.State, test.units, test.services)
   130  		if test.error == "" {
   131  			c.Check(err, gc.IsNil)
   132  			var units []string
   133  			for _, unit := range result {
   134  				units = append(units, unit.Name())
   135  			}
   136  			c.Check(units, jc.SameContents, test.expected)
   137  		} else {
   138  			c.Check(err, gc.ErrorMatches, test.error)
   139  		}
   140  	}
   141  }
   142  
   143  func (s *runSuite) mockSSH(c *gc.C, cmd string) {
   144  	testbin := c.MkDir()
   145  	fakessh := filepath.Join(testbin, "ssh")
   146  	s.PatchEnvPathPrepend(testbin)
   147  	err := ioutil.WriteFile(fakessh, []byte(cmd), 0755)
   148  	c.Assert(err, gc.IsNil)
   149  }
   150  
   151  func (s *runSuite) TestParallelExecuteErrorsOnBlankHost(c *gc.C) {
   152  	s.mockSSH(c, echoInputShowArgs)
   153  
   154  	params := []*client.RemoteExec{
   155  		&client.RemoteExec{
   156  			ExecParams: ssh.ExecParams{
   157  				Command: "foo",
   158  				Timeout: testing.LongWait,
   159  			},
   160  		},
   161  	}
   162  
   163  	runResults := client.ParallelExecute("/some/dir", params)
   164  	c.Assert(runResults.Results, gc.HasLen, 1)
   165  	result := runResults.Results[0]
   166  	c.Assert(result.Error, gc.Equals, "missing host address")
   167  }
   168  
   169  func (s *runSuite) TestParallelExecuteAddsIdentity(c *gc.C) {
   170  	s.mockSSH(c, echoInputShowArgs)
   171  
   172  	params := []*client.RemoteExec{
   173  		&client.RemoteExec{
   174  			ExecParams: ssh.ExecParams{
   175  				Host:    "localhost",
   176  				Command: "foo",
   177  				Timeout: testing.LongWait,
   178  			},
   179  		},
   180  	}
   181  
   182  	runResults := client.ParallelExecute("/some/dir", params)
   183  	c.Assert(runResults.Results, gc.HasLen, 1)
   184  	result := runResults.Results[0]
   185  	c.Assert(result.Error, gc.Equals, "")
   186  	c.Assert(string(result.Stderr), jc.Contains, "-i /some/dir/system-identity")
   187  }
   188  
   189  func (s *runSuite) TestParallelExecuteCopiesAcrossMachineAndUnit(c *gc.C) {
   190  	s.mockSSH(c, echoInputShowArgs)
   191  
   192  	params := []*client.RemoteExec{
   193  		&client.RemoteExec{
   194  			ExecParams: ssh.ExecParams{
   195  				Host:    "localhost",
   196  				Command: "foo",
   197  				Timeout: testing.LongWait,
   198  			},
   199  			MachineId: "machine-id",
   200  			UnitId:    "unit-id",
   201  		},
   202  	}
   203  
   204  	runResults := client.ParallelExecute("/some/dir", params)
   205  	c.Assert(runResults.Results, gc.HasLen, 1)
   206  	result := runResults.Results[0]
   207  	c.Assert(result.Error, gc.Equals, "")
   208  	c.Assert(result.MachineId, gc.Equals, "machine-id")
   209  	c.Assert(result.UnitId, gc.Equals, "unit-id")
   210  }
   211  
   212  func (s *runSuite) TestRunOnAllMachines(c *gc.C) {
   213  	// Make three machines.
   214  	s.addMachineWithAddress(c, "10.3.2.1")
   215  	s.addMachineWithAddress(c, "10.3.2.2")
   216  	s.addMachineWithAddress(c, "10.3.2.3")
   217  
   218  	s.mockSSH(c, echoInput)
   219  
   220  	// hmm... this seems to be going through the api client, and from there
   221  	// through to the apiserver implementation. Not ideal, but it is how the
   222  	// other client tests are written.
   223  	client := s.APIState.Client()
   224  	results, err := client.RunOnAllMachines("hostname", testing.LongWait)
   225  	c.Assert(err, gc.IsNil)
   226  	c.Assert(results, gc.HasLen, 3)
   227  	var expectedResults []params.RunResult
   228  	for i := 0; i < 3; i++ {
   229  		expectedResults = append(expectedResults,
   230  			params.RunResult{
   231  				ExecResponse: exec.ExecResponse{Stdout: []byte("juju-run --no-context 'hostname'\n")},
   232  				MachineId:    fmt.Sprint(i),
   233  			})
   234  	}
   235  
   236  	c.Assert(results, jc.DeepEquals, expectedResults)
   237  }
   238  
   239  func (s *runSuite) TestRunMachineAndService(c *gc.C) {
   240  	// Make three machines.
   241  	s.addMachineWithAddress(c, "10.3.2.1")
   242  
   243  	charm := s.AddTestingCharm(c, "dummy")
   244  	magic, err := s.State.AddService("magic", "user-admin", charm)
   245  	s.addUnit(c, magic)
   246  	s.addUnit(c, magic)
   247  
   248  	s.mockSSH(c, echoInput)
   249  
   250  	// hmm... this seems to be going through the api client, and from there
   251  	// through to the apiserver implementation. Not ideal, but it is how the
   252  	// other client tests are written.
   253  	client := s.APIState.Client()
   254  	results, err := client.Run(
   255  		params.RunParams{
   256  			Commands: "hostname",
   257  			Timeout:  testing.LongWait,
   258  			Machines: []string{"0"},
   259  			Services: []string{"magic"},
   260  		})
   261  	c.Assert(err, gc.IsNil)
   262  	c.Assert(results, gc.HasLen, 3)
   263  	expectedResults := []params.RunResult{
   264  		params.RunResult{
   265  			ExecResponse: exec.ExecResponse{Stdout: []byte("juju-run --no-context 'hostname'\n")},
   266  			MachineId:    "0",
   267  		},
   268  		params.RunResult{
   269  			ExecResponse: exec.ExecResponse{Stdout: []byte("juju-run magic/0 'hostname'\n")},
   270  			MachineId:    "1",
   271  			UnitId:       "magic/0",
   272  		},
   273  		params.RunResult{
   274  			ExecResponse: exec.ExecResponse{Stdout: []byte("juju-run magic/1 'hostname'\n")},
   275  			MachineId:    "2",
   276  			UnitId:       "magic/1",
   277  		},
   278  	}
   279  
   280  	c.Assert(results, jc.DeepEquals, expectedResults)
   281  }
   282  
   283  var echoInputShowArgs = `#!/bin/bash
   284  # Write the args to stderr
   285  echo "$*" >&2
   286  # And echo stdin to stdout
   287  while read line
   288  do echo $line
   289  done <&0
   290  `
   291  
   292  var echoInput = `#!/bin/bash
   293  # And echo stdin to stdout
   294  while read line
   295  do echo $line
   296  done <&0
   297  `