github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/cmd/juju/commands/ensureavailability_test.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package commands
     5  
     6  import (
     7  	"bytes"
     8  	"encoding/json"
     9  	"fmt"
    10  	"strings"
    11  
    12  	"github.com/juju/cmd"
    13  	jc "github.com/juju/testing/checkers"
    14  	gc "gopkg.in/check.v1"
    15  	goyaml "gopkg.in/yaml.v1"
    16  
    17  	"github.com/juju/juju/apiserver/common"
    18  	"github.com/juju/juju/apiserver/params"
    19  	"github.com/juju/juju/cmd/envcmd"
    20  	"github.com/juju/juju/constraints"
    21  	"github.com/juju/juju/instance"
    22  	"github.com/juju/juju/juju/testing"
    23  	"github.com/juju/juju/state"
    24  	coretesting "github.com/juju/juju/testing"
    25  	"github.com/juju/juju/testing/factory"
    26  )
    27  
    28  type EnsureAvailabilitySuite struct {
    29  	// TODO (cherylj) change this back to a FakeJujuHomeSuite to
    30  	// remove the mongo dependency once ensure-availability is
    31  	// moved under a supercommand again.
    32  	testing.JujuConnSuite
    33  	fake *fakeHAClient
    34  }
    35  
    36  // invalidNumServers is a number of state servers that would
    37  // never be generated by the ensure-availability command.
    38  const invalidNumServers = -2
    39  
    40  func (s *EnsureAvailabilitySuite) SetUpTest(c *gc.C) {
    41  	s.JujuConnSuite.SetUpTest(c)
    42  
    43  	// Initialize numStateServers to an invalid number to validate
    44  	// that ensure-availability doesn't call into the API when its
    45  	// pre-checks fail
    46  	s.fake = &fakeHAClient{numStateServers: invalidNumServers}
    47  }
    48  
    49  type fakeHAClient struct {
    50  	numStateServers int
    51  	cons            constraints.Value
    52  	err             error
    53  	series          string
    54  	placement       []string
    55  	result          params.StateServersChanges
    56  }
    57  
    58  func (f *fakeHAClient) Close() error {
    59  	return nil
    60  }
    61  
    62  func (f *fakeHAClient) EnsureAvailability(numStateServers int, cons constraints.Value,
    63  	series string, placement []string) (params.StateServersChanges, error) {
    64  
    65  	f.numStateServers = numStateServers
    66  	f.cons = cons
    67  	f.series = series
    68  	f.placement = placement
    69  
    70  	if f.err != nil {
    71  		return f.result, f.err
    72  	}
    73  
    74  	if numStateServers == 1 {
    75  		return f.result, nil
    76  	}
    77  
    78  	// In the real HAClient, specifying a numStateServers value of 0
    79  	// indicates that the default value (3) should be used
    80  	if numStateServers == 0 {
    81  		numStateServers = 3
    82  	}
    83  
    84  	f.result.Maintained = append(f.result.Maintained, "machine-0")
    85  
    86  	for _, p := range placement {
    87  		m, err := instance.ParsePlacement(p)
    88  		if err == nil && m.Scope == instance.MachineScope {
    89  			f.result.Converted = append(f.result.Converted, "machine-"+m.Directive)
    90  		}
    91  	}
    92  
    93  	// We may need to pretend that we added some machines.
    94  	for i := len(f.result.Converted) + 1; i < numStateServers; i++ {
    95  		f.result.Added = append(f.result.Added, fmt.Sprintf("machine-%d", i))
    96  	}
    97  
    98  	return f.result, nil
    99  }
   100  
   101  var _ = gc.Suite(&EnsureAvailabilitySuite{})
   102  
   103  func (s *EnsureAvailabilitySuite) runEnsureAvailability(c *gc.C, args ...string) (*cmd.Context, error) {
   104  	command := &EnsureAvailabilityCommand{haClient: s.fake}
   105  	return coretesting.RunCommand(c, envcmd.Wrap(command), args...)
   106  }
   107  
   108  func (s *EnsureAvailabilitySuite) TestEnsureAvailability(c *gc.C) {
   109  	ctx, err := s.runEnsureAvailability(c, "-n", "1")
   110  	c.Assert(err, jc.ErrorIsNil)
   111  	c.Assert(coretesting.Stdout(ctx), gc.Equals, "")
   112  
   113  	c.Assert(s.fake.numStateServers, gc.Equals, 1)
   114  	c.Assert(&s.fake.cons, jc.Satisfies, constraints.IsEmpty)
   115  	c.Assert(s.fake.series, gc.Equals, "")
   116  	c.Assert(len(s.fake.placement), gc.Equals, 0)
   117  }
   118  
   119  func (s *EnsureAvailabilitySuite) TestBlockEnsureAvailability(c *gc.C) {
   120  	s.fake.err = common.ErrOperationBlocked("TestBlockEnsureAvailability")
   121  	_, err := s.runEnsureAvailability(c, "-n", "1")
   122  	c.Assert(err, gc.ErrorMatches, cmd.ErrSilent.Error())
   123  
   124  	// msg is logged
   125  	stripped := strings.Replace(c.GetTestLog(), "\n", "", -1)
   126  	c.Check(stripped, gc.Matches, ".*TestBlockEnsureAvailability.*")
   127  }
   128  
   129  func (s *EnsureAvailabilitySuite) TestEnsureAvailabilityFormatYaml(c *gc.C) {
   130  	expected := map[string][]string{
   131  		"maintained": {"0"},
   132  		"added":      {"1", "2"},
   133  	}
   134  
   135  	ctx, err := s.runEnsureAvailability(c, "-n", "3", "--format", "yaml")
   136  	c.Assert(err, jc.ErrorIsNil)
   137  
   138  	c.Assert(s.fake.numStateServers, gc.Equals, 3)
   139  	c.Assert(&s.fake.cons, jc.Satisfies, constraints.IsEmpty)
   140  	c.Assert(s.fake.series, gc.Equals, "")
   141  	c.Assert(len(s.fake.placement), gc.Equals, 0)
   142  
   143  	var result map[string][]string
   144  	err = goyaml.Unmarshal(ctx.Stdout.(*bytes.Buffer).Bytes(), &result)
   145  	c.Assert(err, jc.ErrorIsNil)
   146  	c.Assert(result, gc.DeepEquals, expected)
   147  }
   148  
   149  func (s *EnsureAvailabilitySuite) TestEnsureAvailabilityFormatJson(c *gc.C) {
   150  	expected := map[string][]string{
   151  		"maintained": {"0"},
   152  		"added":      {"1", "2"},
   153  	}
   154  
   155  	ctx, err := s.runEnsureAvailability(c, "-n", "3", "--format", "json")
   156  	c.Assert(err, jc.ErrorIsNil)
   157  
   158  	c.Assert(s.fake.numStateServers, gc.Equals, 3)
   159  	c.Assert(&s.fake.cons, jc.Satisfies, constraints.IsEmpty)
   160  	c.Assert(s.fake.series, gc.Equals, "")
   161  	c.Assert(len(s.fake.placement), gc.Equals, 0)
   162  
   163  	var result map[string][]string
   164  	err = json.Unmarshal(ctx.Stdout.(*bytes.Buffer).Bytes(), &result)
   165  	c.Assert(err, jc.ErrorIsNil)
   166  	c.Assert(result, gc.DeepEquals, expected)
   167  }
   168  
   169  func (s *EnsureAvailabilitySuite) TestEnsureAvailabilityWithSeries(c *gc.C) {
   170  	// Also test with -n 5 to validate numbers other than 1 and 3
   171  	ctx, err := s.runEnsureAvailability(c, "--series", "series", "-n", "5")
   172  	c.Assert(err, jc.ErrorIsNil)
   173  	c.Assert(coretesting.Stdout(ctx), gc.Equals,
   174  		"maintaining machines: 0\n"+
   175  			"adding machines: 1, 2, 3, 4\n\n")
   176  
   177  	c.Assert(s.fake.numStateServers, gc.Equals, 5)
   178  	c.Assert(&s.fake.cons, jc.Satisfies, constraints.IsEmpty)
   179  	c.Assert(s.fake.series, gc.Equals, "series")
   180  	c.Assert(len(s.fake.placement), gc.Equals, 0)
   181  }
   182  
   183  func (s *EnsureAvailabilitySuite) TestEnsureAvailabilityWithConstraints(c *gc.C) {
   184  	ctx, err := s.runEnsureAvailability(c, "--constraints", "mem=4G", "-n", "3")
   185  	c.Assert(err, jc.ErrorIsNil)
   186  	c.Assert(coretesting.Stdout(ctx), gc.Equals,
   187  		"maintaining machines: 0\n"+
   188  			"adding machines: 1, 2\n\n")
   189  
   190  	c.Assert(s.fake.numStateServers, gc.Equals, 3)
   191  	expectedCons := constraints.MustParse("mem=4G")
   192  	c.Assert(s.fake.cons, gc.DeepEquals, expectedCons)
   193  	c.Assert(s.fake.series, gc.Equals, "")
   194  	c.Assert(len(s.fake.placement), gc.Equals, 0)
   195  }
   196  
   197  func (s *EnsureAvailabilitySuite) TestEnsureAvailabilityWithPlacement(c *gc.C) {
   198  	ctx, err := s.runEnsureAvailability(c, "--to", "valid", "-n", "3")
   199  	c.Assert(err, jc.ErrorIsNil)
   200  	c.Assert(coretesting.Stdout(ctx), gc.Equals,
   201  		"maintaining machines: 0\n"+
   202  			"adding machines: 1, 2\n\n")
   203  
   204  	c.Assert(s.fake.numStateServers, gc.Equals, 3)
   205  	c.Assert(&s.fake.cons, jc.Satisfies, constraints.IsEmpty)
   206  	c.Assert(s.fake.series, gc.Equals, "")
   207  	expectedPlacement := []string{"valid"}
   208  	c.Assert(s.fake.placement, gc.DeepEquals, expectedPlacement)
   209  }
   210  
   211  func (s *EnsureAvailabilitySuite) TestEnsureAvailabilityErrors(c *gc.C) {
   212  	for _, n := range []int{-1, 2} {
   213  		_, err := s.runEnsureAvailability(c, "-n", fmt.Sprint(n))
   214  		c.Assert(err, gc.ErrorMatches, "must specify a number of state servers odd and non-negative")
   215  	}
   216  
   217  	// Verify that ensure-availability didn't call into the API
   218  	c.Assert(s.fake.numStateServers, gc.Equals, invalidNumServers)
   219  }
   220  
   221  func (s *EnsureAvailabilitySuite) TestEnsureAvailabilityAllows0(c *gc.C) {
   222  	// If the number of state servers is specified as "0", the API will
   223  	// then use the default number of 3.
   224  	ctx, err := s.runEnsureAvailability(c, "-n", "0")
   225  	c.Assert(err, jc.ErrorIsNil)
   226  	c.Assert(coretesting.Stdout(ctx), gc.Equals,
   227  		"maintaining machines: 0\n"+
   228  			"adding machines: 1, 2\n\n")
   229  
   230  	c.Assert(s.fake.numStateServers, gc.Equals, 0)
   231  	c.Assert(&s.fake.cons, jc.Satisfies, constraints.IsEmpty)
   232  	c.Assert(s.fake.series, gc.Equals, "")
   233  	c.Assert(len(s.fake.placement), gc.Equals, 0)
   234  }
   235  
   236  func (s *EnsureAvailabilitySuite) TestEnsureAvailabilityDefaultsTo0(c *gc.C) {
   237  	// If the number of state servers is not specified, we pass in 0 to the
   238  	// API.  The API will then use the default number of 3.
   239  	ctx, err := s.runEnsureAvailability(c)
   240  	c.Assert(err, jc.ErrorIsNil)
   241  	c.Assert(coretesting.Stdout(ctx), gc.Equals,
   242  		"maintaining machines: 0\n"+
   243  			"adding machines: 1, 2\n\n")
   244  
   245  	c.Assert(s.fake.numStateServers, gc.Equals, 0)
   246  	c.Assert(&s.fake.cons, jc.Satisfies, constraints.IsEmpty)
   247  	c.Assert(s.fake.series, gc.Equals, "")
   248  	c.Assert(len(s.fake.placement), gc.Equals, 0)
   249  }
   250  
   251  func (s *EnsureAvailabilitySuite) TestEnsureAvailabilityEndToEnd(c *gc.C) {
   252  	s.Factory.MakeMachine(c, &factory.MachineParams{
   253  		Jobs: []state.MachineJob{state.JobManageEnviron},
   254  	})
   255  	ctx, err := coretesting.RunCommand(c, envcmd.Wrap(&EnsureAvailabilityCommand{}), "-n", "3")
   256  	c.Assert(err, jc.ErrorIsNil)
   257  
   258  	// Machine 0 is demoted because it hasn't reported its presence
   259  	c.Assert(coretesting.Stdout(ctx), gc.Equals,
   260  		"adding machines: 1, 2, 3\n"+
   261  			"demoting machines: 0\n\n")
   262  }
   263  
   264  func (s *EnsureAvailabilitySuite) TestEnsureAvailabilityToExisting(c *gc.C) {
   265  	ctx, err := s.runEnsureAvailability(c, "--to", "1,2")
   266  	c.Assert(err, jc.ErrorIsNil)
   267  	c.Check(coretesting.Stdout(ctx), gc.Equals, `
   268  maintaining machines: 0
   269  converting machines: 1, 2
   270  
   271  `[1:])
   272  
   273  	c.Check(s.fake.numStateServers, gc.Equals, 0)
   274  	c.Check(&s.fake.cons, jc.Satisfies, constraints.IsEmpty)
   275  	c.Check(s.fake.series, gc.Equals, "")
   276  	c.Check(len(s.fake.placement), gc.Equals, 2)
   277  }