github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/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  	"time"
     9  
    10  	"github.com/juju/errors"
    11  	gitjujutesting "github.com/juju/testing"
    12  	jc "github.com/juju/testing/checkers"
    13  	"github.com/juju/utils/exec"
    14  	gc "gopkg.in/check.v1"
    15  
    16  	"github.com/juju/juju/apiserver/client"
    17  	"github.com/juju/juju/apiserver/common"
    18  	"github.com/juju/juju/apiserver/params"
    19  	"github.com/juju/juju/network"
    20  	"github.com/juju/juju/state"
    21  	"github.com/juju/juju/testing"
    22  	"github.com/juju/juju/utils/ssh"
    23  )
    24  
    25  type runSuite struct {
    26  	baseSuite
    27  }
    28  
    29  var _ = gc.Suite(&runSuite{})
    30  
    31  func (s *runSuite) addMachine(c *gc.C) *state.Machine {
    32  	machine, err := s.State.AddMachine("quantal", state.JobHostUnits)
    33  	c.Assert(err, jc.ErrorIsNil)
    34  	return machine
    35  }
    36  
    37  func (s *runSuite) addMachineWithAddress(c *gc.C, address string) *state.Machine {
    38  	machine := s.addMachine(c)
    39  	machine.SetAddresses(network.NewAddress(address, network.ScopeUnknown))
    40  	return machine
    41  }
    42  
    43  func (s *runSuite) TestRemoteParamsForMachinePopulates(c *gc.C) {
    44  	machine := s.addMachine(c)
    45  	result := client.RemoteParamsForMachine(machine, "command", time.Minute)
    46  	c.Assert(result.Command, gc.Equals, "command")
    47  	c.Assert(result.Timeout, gc.Equals, time.Minute)
    48  	c.Assert(result.MachineId, gc.Equals, machine.Id())
    49  	// Now an empty host isn't particularly useful, but the machine doesn't
    50  	// have an address to use.
    51  	c.Assert(machine.Addresses(), gc.HasLen, 0)
    52  	c.Assert(result.Host, gc.Equals, "")
    53  }
    54  
    55  // blockAllChanges blocks all operations that could change environment -
    56  // setting block-all-changes to true.
    57  // Asserts that no errors were encountered.
    58  func (s *runSuite) blockAllChanges(c *gc.C) {
    59  	err := s.State.UpdateEnvironConfig(map[string]interface{}{"block-all-changes": true}, nil, nil)
    60  	c.Assert(err, jc.ErrorIsNil)
    61  }
    62  
    63  func (s *runSuite) TestRemoteParamsForMachinePopulatesWithAddress(c *gc.C) {
    64  	machine := s.addMachineWithAddress(c, "10.3.2.1")
    65  
    66  	result := client.RemoteParamsForMachine(machine, "command", time.Minute)
    67  	c.Assert(result.Command, gc.Equals, "command")
    68  	c.Assert(result.Timeout, gc.Equals, time.Minute)
    69  	c.Assert(result.MachineId, gc.Equals, machine.Id())
    70  	c.Assert(result.Host, gc.Equals, "ubuntu@10.3.2.1")
    71  }
    72  
    73  func (s *runSuite) addUnit(c *gc.C, service *state.Service) *state.Unit {
    74  	unit, err := service.AddUnit()
    75  	c.Assert(err, jc.ErrorIsNil)
    76  	err = unit.AssignToNewMachine()
    77  	c.Assert(err, jc.ErrorIsNil)
    78  	mId, err := unit.AssignedMachineId()
    79  	c.Assert(err, jc.ErrorIsNil)
    80  	machine, err := s.State.Machine(mId)
    81  	c.Assert(err, jc.ErrorIsNil)
    82  	machine.SetAddresses(network.NewAddress("10.3.2.1", network.ScopeUnknown))
    83  	return unit
    84  }
    85  
    86  func (s *runSuite) TestGetAllUnitNames(c *gc.C) {
    87  	charm := s.AddTestingCharm(c, "dummy")
    88  	owner := s.AdminUserTag(c)
    89  	magic, err := s.State.AddService("magic", owner.String(), charm, nil, nil)
    90  	s.addUnit(c, magic)
    91  	s.addUnit(c, magic)
    92  
    93  	notAssigned, err := s.State.AddService("not-assigned", owner.String(), charm, nil, nil)
    94  	c.Assert(err, jc.ErrorIsNil)
    95  	_, err = notAssigned.AddUnit()
    96  	c.Assert(err, jc.ErrorIsNil)
    97  
    98  	_, err = s.State.AddService("no-units", owner.String(), charm, nil, nil)
    99  	c.Assert(err, jc.ErrorIsNil)
   100  
   101  	wordpress, err := s.State.AddService("wordpress", owner.String(), s.AddTestingCharm(c, "wordpress"), nil, nil)
   102  	c.Assert(err, jc.ErrorIsNil)
   103  	wordpress0 := s.addUnit(c, wordpress)
   104  	_, err = s.State.AddService("logging", owner.String(), s.AddTestingCharm(c, "logging"), nil, nil)
   105  	c.Assert(err, jc.ErrorIsNil)
   106  
   107  	eps, err := s.State.InferEndpoints("logging", "wordpress")
   108  	c.Assert(err, jc.ErrorIsNil)
   109  	rel, err := s.State.AddRelation(eps...)
   110  	c.Assert(err, jc.ErrorIsNil)
   111  	ru, err := rel.Unit(wordpress0)
   112  	c.Assert(err, jc.ErrorIsNil)
   113  	err = ru.EnterScope(nil)
   114  	c.Assert(err, jc.ErrorIsNil)
   115  
   116  	for i, test := range []struct {
   117  		message  string
   118  		expected []string
   119  		units    []string
   120  		services []string
   121  		error    string
   122  	}{{
   123  		message: "no units, expected nil slice",
   124  	}, {
   125  		message: "asking for a unit that isn't there",
   126  		units:   []string{"foo/0"},
   127  		error:   `unit "foo/0" not found`,
   128  	}, {
   129  		message:  "asking for a service that isn't there",
   130  		services: []string{"foo"},
   131  		error:    `service "foo" not found`,
   132  	}, {
   133  		message:  "service with no units is not really an error",
   134  		services: []string{"no-units"},
   135  	}, {
   136  		message:  "A service with units not assigned is an error",
   137  		services: []string{"not-assigned"},
   138  		error:    `unit "not-assigned/0" is not assigned to a machine`,
   139  	}, {
   140  		message:  "A service with units",
   141  		services: []string{"magic"},
   142  		expected: []string{"magic/0", "magic/1"},
   143  	}, {
   144  		message:  "Asking for just a unit",
   145  		units:    []string{"magic/0"},
   146  		expected: []string{"magic/0"},
   147  	}, {
   148  		message:  "Asking for just a subordinate unit",
   149  		units:    []string{"logging/0"},
   150  		expected: []string{"logging/0"},
   151  	}, {
   152  		message:  "Asking for a unit, and the service",
   153  		services: []string{"magic"},
   154  		units:    []string{"magic/0"},
   155  		expected: []string{"magic/0", "magic/1"},
   156  	}} {
   157  		c.Logf("%v: %s", i, test.message)
   158  		result, err := client.GetAllUnitNames(s.State, test.units, test.services)
   159  		if test.error == "" {
   160  			c.Check(err, jc.ErrorIsNil)
   161  			var units []string
   162  			for _, unit := range result {
   163  				units = append(units, unit.Name())
   164  			}
   165  			c.Check(units, jc.SameContents, test.expected)
   166  		} else {
   167  			c.Check(err, gc.ErrorMatches, test.error)
   168  		}
   169  	}
   170  }
   171  
   172  func (s *runSuite) mockSSH(c *gc.C, cmd string) {
   173  	gitjujutesting.PatchExecutable(c, s, "ssh", cmd)
   174  	gitjujutesting.PatchExecutable(c, s, "scp", cmd)
   175  	client, _ := ssh.NewOpenSSHClient()
   176  	s.PatchValue(&ssh.DefaultClient, client)
   177  }
   178  
   179  func (s *runSuite) TestParallelExecuteErrorsOnBlankHost(c *gc.C) {
   180  	s.mockSSH(c, echoInputShowArgs)
   181  
   182  	params := []*client.RemoteExec{
   183  		{
   184  			ExecParams: ssh.ExecParams{
   185  				Command: "foo",
   186  				Timeout: testing.LongWait,
   187  			},
   188  		},
   189  	}
   190  
   191  	runResults := client.ParallelExecute("/some/dir", params)
   192  	c.Assert(runResults.Results, gc.HasLen, 1)
   193  	result := runResults.Results[0]
   194  	c.Assert(result.Error, gc.Equals, "missing host address")
   195  }
   196  
   197  func (s *runSuite) TestParallelExecuteAddsIdentity(c *gc.C) {
   198  	s.mockSSH(c, echoInputShowArgs)
   199  
   200  	params := []*client.RemoteExec{
   201  		{
   202  			ExecParams: ssh.ExecParams{
   203  				Host:    "localhost",
   204  				Command: "foo",
   205  				Timeout: testing.LongWait,
   206  			},
   207  		},
   208  	}
   209  
   210  	runResults := client.ParallelExecute("/some/dir", params)
   211  	c.Assert(runResults.Results, gc.HasLen, 1)
   212  	result := runResults.Results[0]
   213  	c.Assert(result.Error, gc.Equals, "")
   214  	c.Assert(string(result.Stderr), jc.Contains, "system-identity")
   215  }
   216  
   217  func (s *runSuite) TestParallelExecuteCopiesAcrossMachineAndUnit(c *gc.C) {
   218  	s.mockSSH(c, echoInputShowArgs)
   219  
   220  	params := []*client.RemoteExec{
   221  		{
   222  			ExecParams: ssh.ExecParams{
   223  				Host:    "localhost",
   224  				Command: "foo",
   225  				Timeout: testing.LongWait,
   226  			},
   227  			MachineId: "machine-id",
   228  			UnitId:    "unit-id",
   229  		},
   230  	}
   231  
   232  	runResults := client.ParallelExecute("/some/dir", params)
   233  	c.Assert(runResults.Results, gc.HasLen, 1)
   234  	result := runResults.Results[0]
   235  	c.Assert(result.Error, gc.Equals, "")
   236  	c.Assert(result.MachineId, gc.Equals, "machine-id")
   237  	c.Assert(result.UnitId, gc.Equals, "unit-id")
   238  }
   239  
   240  func (s *runSuite) TestRunOnAllMachines(c *gc.C) {
   241  	// Make three machines.
   242  	s.addMachineWithAddress(c, "10.3.2.1")
   243  	s.addMachineWithAddress(c, "10.3.2.2")
   244  	s.addMachineWithAddress(c, "10.3.2.3")
   245  
   246  	s.mockSSH(c, echoInput)
   247  
   248  	// hmm... this seems to be going through the api client, and from there
   249  	// through to the apiserver implementation. Not ideal, but it is how the
   250  	// other client tests are written.
   251  	client := s.APIState.Client()
   252  	results, err := client.RunOnAllMachines("hostname", testing.LongWait)
   253  	c.Assert(err, jc.ErrorIsNil)
   254  	c.Assert(results, gc.HasLen, 3)
   255  
   256  	var expectedResults []params.RunResult
   257  	for i := 0; i < 3; i++ {
   258  		expectedResults = append(expectedResults,
   259  			params.RunResult{
   260  				ExecResponse: exec.ExecResponse{Stdout: []byte(expectedCommand[0])},
   261  
   262  				MachineId: fmt.Sprint(i),
   263  			})
   264  	}
   265  
   266  	c.Assert(results, jc.DeepEquals, expectedResults)
   267  }
   268  
   269  func (s *runSuite) TestBlockRunOnAllMachines(c *gc.C) {
   270  	// Make three machines.
   271  	s.addMachineWithAddress(c, "10.3.2.1")
   272  	s.addMachineWithAddress(c, "10.3.2.2")
   273  	s.addMachineWithAddress(c, "10.3.2.3")
   274  
   275  	s.mockSSH(c, echoInput)
   276  
   277  	// block all changes
   278  	s.blockAllChanges(c)
   279  	client := s.APIState.Client()
   280  	_, err := client.RunOnAllMachines("hostname", testing.LongWait)
   281  	c.Assert(errors.Cause(err), gc.DeepEquals, common.ErrOperationBlocked)
   282  }
   283  
   284  func (s *runSuite) TestRunMachineAndService(c *gc.C) {
   285  	// Make three machines.
   286  	s.addMachineWithAddress(c, "10.3.2.1")
   287  
   288  	charm := s.AddTestingCharm(c, "dummy")
   289  	owner := s.Factory.MakeUser(c, nil).Tag()
   290  	magic, err := s.State.AddService("magic", owner.String(), charm, nil, nil)
   291  	c.Assert(err, jc.ErrorIsNil)
   292  	s.addUnit(c, magic)
   293  	s.addUnit(c, magic)
   294  
   295  	s.mockSSH(c, echoInput)
   296  
   297  	// hmm... this seems to be going through the api client, and from there
   298  	// through to the apiserver implementation. Not ideal, but it is how the
   299  	// other client tests are written.
   300  	client := s.APIState.Client()
   301  	results, err := client.Run(
   302  		params.RunParams{
   303  			Commands: "hostname",
   304  			Timeout:  testing.LongWait,
   305  			Machines: []string{"0"},
   306  			Services: []string{"magic"},
   307  		})
   308  	c.Assert(err, jc.ErrorIsNil)
   309  	c.Assert(results, gc.HasLen, 3)
   310  
   311  	expectedResults := []params.RunResult{
   312  		{
   313  			ExecResponse: exec.ExecResponse{Stdout: []byte(expectedCommand[0])},
   314  			MachineId:    "0",
   315  		},
   316  		{
   317  			ExecResponse: exec.ExecResponse{Stdout: []byte(expectedCommand[1])},
   318  			MachineId:    "1",
   319  			UnitId:       "magic/0",
   320  		},
   321  		{
   322  			ExecResponse: exec.ExecResponse{Stdout: []byte(expectedCommand[2])},
   323  			MachineId:    "2",
   324  			UnitId:       "magic/1",
   325  		},
   326  	}
   327  
   328  	c.Assert(results, jc.DeepEquals, expectedResults)
   329  }
   330  
   331  func (s *runSuite) TestBlockRunMachineAndService(c *gc.C) {
   332  	// Make three machines.
   333  	s.addMachineWithAddress(c, "10.3.2.1")
   334  
   335  	charm := s.AddTestingCharm(c, "dummy")
   336  	owner := s.Factory.MakeUser(c, nil).Tag()
   337  	magic, err := s.State.AddService("magic", owner.String(), charm, nil, nil)
   338  	c.Assert(err, jc.ErrorIsNil)
   339  	s.addUnit(c, magic)
   340  	s.addUnit(c, magic)
   341  
   342  	s.mockSSH(c, echoInput)
   343  
   344  	// hmm... this seems to be going through the api client, and from there
   345  	// through to the apiserver implementation. Not ideal, but it is how the
   346  	// other client tests are written.
   347  	client := s.APIState.Client()
   348  
   349  	// block all changes
   350  	s.blockAllChanges(c)
   351  	_, err = client.Run(
   352  		params.RunParams{
   353  			Commands: "hostname",
   354  			Timeout:  testing.LongWait,
   355  			Machines: []string{"0"},
   356  			Services: []string{"magic"},
   357  		})
   358  	c.Assert(errors.Cause(err), gc.DeepEquals, common.ErrOperationBlocked)
   359  }