github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/worker/apicaller/connect_test.go (about)

     1  // Copyright 2012-2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package apicaller_test
     5  
     6  import (
     7  	"errors"
     8  
     9  	"github.com/juju/names"
    10  	"github.com/juju/testing"
    11  	jc "github.com/juju/testing/checkers"
    12  	gc "gopkg.in/check.v1"
    13  
    14  	"github.com/juju/juju/agent"
    15  	"github.com/juju/juju/api"
    16  	apiagent "github.com/juju/juju/api/agent"
    17  	"github.com/juju/juju/apiserver/params"
    18  	coretesting "github.com/juju/juju/testing"
    19  	"github.com/juju/juju/worker/apicaller"
    20  )
    21  
    22  // ScaryConnectSuite should cover all the *lines* where we get a connection
    23  // without triggering the checkProvisionedStrategy ugliness. It tests the
    24  // various conditions in isolation; it's possible that some real scenarios
    25  // may trigger more than one of these, but it's impractical to test *every*
    26  // possible *path*.
    27  type ScaryConnectSuite struct {
    28  	testing.IsolationSuite
    29  }
    30  
    31  var _ = gc.Suite(&ScaryConnectSuite{})
    32  
    33  func (*ScaryConnectSuite) TestEntityAlive(c *gc.C) {
    34  	testEntityFine(c, apiagent.Alive)
    35  }
    36  
    37  func (*ScaryConnectSuite) TestEntityDying(c *gc.C) {
    38  	testEntityFine(c, apiagent.Dying)
    39  }
    40  
    41  func testEntityFine(c *gc.C, life apiagent.Life) {
    42  	stub := &testing.Stub{}
    43  	expectConn := &mockConn{stub: stub}
    44  	apiOpen := func(info *api.Info, opts api.DialOpts) (api.Connection, error) {
    45  		// no apiOpen stub calls necessary in this suite; covered
    46  		// by RetrySuite, just an extra complication here.
    47  		return expectConn, nil
    48  	}
    49  
    50  	// to make the point that this code should be entity-agnostic,
    51  	// use an entity that doesn't correspond to an agent at all.
    52  	entity := names.NewServiceTag("omg")
    53  	connect := func() (api.Connection, error) {
    54  		return apicaller.ScaryConnect(&mockAgent{
    55  			stub:   stub,
    56  			model:  coretesting.ModelTag,
    57  			entity: entity,
    58  		}, apiOpen)
    59  	}
    60  
    61  	conn, err := lifeTest(c, stub, apiagent.Alive, connect)
    62  	c.Check(conn, gc.Equals, expectConn)
    63  	c.Check(err, jc.ErrorIsNil)
    64  	stub.CheckCalls(c, []testing.StubCall{{
    65  		FuncName: "Life",
    66  		Args:     []interface{}{entity},
    67  	}, {
    68  		FuncName: "SetPassword",
    69  		Args:     []interface{}{entity, "new"},
    70  	}})
    71  }
    72  
    73  func (*ScaryConnectSuite) TestModelTagCannotChangeConfig(c *gc.C) {
    74  	stub := checkModelTagUpdate(c, errors.New("oh noes"))
    75  	stub.CheckCallNames(c,
    76  		"ChangeConfig",
    77  		"Life", "SetPassword",
    78  	)
    79  }
    80  
    81  func (*ScaryConnectSuite) TestModelTagCannotGetTag(c *gc.C) {
    82  	stub := checkModelTagUpdate(c, nil, errors.New("oh noes"))
    83  	stub.CheckCallNames(c,
    84  		"ChangeConfig", "ModelTag",
    85  		"Life", "SetPassword",
    86  	)
    87  }
    88  
    89  func (*ScaryConnectSuite) TestModelTagCannotMigrate(c *gc.C) {
    90  	stub := checkModelTagUpdate(c, nil, nil, errors.New("oh noes"))
    91  	stub.CheckCallNames(c,
    92  		"ChangeConfig", "ModelTag", "Migrate",
    93  		"Life", "SetPassword",
    94  	)
    95  	c.Check(stub.Calls()[2].Args, jc.DeepEquals, []interface{}{
    96  		agent.MigrateParams{Model: coretesting.ModelTag},
    97  	})
    98  }
    99  
   100  func (*ScaryConnectSuite) TestModelTagSuccess(c *gc.C) {
   101  	stub := checkModelTagUpdate(c)
   102  	stub.CheckCallNames(c,
   103  		"ChangeConfig", "ModelTag", "Migrate",
   104  		"Life", "SetPassword",
   105  	)
   106  	c.Check(stub.Calls()[2].Args, jc.DeepEquals, []interface{}{
   107  		agent.MigrateParams{Model: coretesting.ModelTag},
   108  	})
   109  }
   110  
   111  func checkModelTagUpdate(c *gc.C, errs ...error) *testing.Stub {
   112  	// success case; just a little failure we don't mind, otherwise
   113  	// equivalent to testEntityFine.
   114  	stub := &testing.Stub{}
   115  	stub.SetErrors(errs...) // from ChangeConfig
   116  	expectConn := &mockConn{stub: stub}
   117  	apiOpen := func(info *api.Info, opts api.DialOpts) (api.Connection, error) {
   118  		return expectConn, nil
   119  	}
   120  
   121  	entity := names.NewServiceTag("omg")
   122  	connect := func() (api.Connection, error) {
   123  		return apicaller.ScaryConnect(&mockAgent{
   124  			stub: stub,
   125  			// no model set; triggers ChangeConfig
   126  			entity: entity,
   127  		}, apiOpen)
   128  	}
   129  	conn, err := lifeTest(c, stub, apiagent.Alive, connect)
   130  	c.Check(conn, gc.Equals, expectConn)
   131  	c.Check(err, jc.ErrorIsNil)
   132  	return stub
   133  }
   134  
   135  func (*ScaryConnectSuite) TestEntityDead(c *gc.C) {
   136  	// permanent failure case
   137  	stub := &testing.Stub{}
   138  	expectConn := &mockConn{stub: stub}
   139  	apiOpen := func(info *api.Info, opts api.DialOpts) (api.Connection, error) {
   140  		return expectConn, nil
   141  	}
   142  
   143  	entity := names.NewServiceTag("omg")
   144  	connect := func() (api.Connection, error) {
   145  		return apicaller.ScaryConnect(&mockAgent{
   146  			stub:   stub,
   147  			model:  coretesting.ModelTag,
   148  			entity: entity,
   149  		}, apiOpen)
   150  	}
   151  
   152  	conn, err := lifeTest(c, stub, apiagent.Dead, connect)
   153  	c.Check(conn, gc.IsNil)
   154  	c.Check(err, gc.Equals, apicaller.ErrConnectImpossible)
   155  	stub.CheckCalls(c, []testing.StubCall{{
   156  		FuncName: "Life",
   157  		Args:     []interface{}{entity},
   158  	}, {
   159  		FuncName: "Close",
   160  	}})
   161  }
   162  
   163  func (*ScaryConnectSuite) TestEntityDenied(c *gc.C) {
   164  	// permanent failure case
   165  	stub := &testing.Stub{}
   166  	stub.SetErrors(apiagent.ErrDenied)
   167  	expectConn := &mockConn{stub: stub}
   168  	apiOpen := func(info *api.Info, opts api.DialOpts) (api.Connection, error) {
   169  		return expectConn, nil
   170  	}
   171  
   172  	entity := names.NewServiceTag("omg")
   173  	connect := func() (api.Connection, error) {
   174  		return apicaller.ScaryConnect(&mockAgent{
   175  			stub:   stub,
   176  			model:  coretesting.ModelTag,
   177  			entity: entity,
   178  		}, apiOpen)
   179  	}
   180  
   181  	conn, err := lifeTest(c, stub, apiagent.Dead, connect)
   182  	c.Check(conn, gc.IsNil)
   183  	c.Check(err, gc.Equals, apicaller.ErrConnectImpossible)
   184  	stub.CheckCalls(c, []testing.StubCall{{
   185  		FuncName: "Life",
   186  		Args:     []interface{}{entity},
   187  	}, {
   188  		FuncName: "Close",
   189  	}})
   190  }
   191  
   192  func (*ScaryConnectSuite) TestEntityUnknownLife(c *gc.C) {
   193  	// "random" failure case
   194  	stub := &testing.Stub{}
   195  	expectConn := &mockConn{stub: stub}
   196  	apiOpen := func(info *api.Info, opts api.DialOpts) (api.Connection, error) {
   197  		return expectConn, nil
   198  	}
   199  
   200  	entity := names.NewServiceTag("omg")
   201  	connect := func() (api.Connection, error) {
   202  		return apicaller.ScaryConnect(&mockAgent{
   203  			stub:   stub,
   204  			model:  coretesting.ModelTag,
   205  			entity: entity,
   206  		}, apiOpen)
   207  	}
   208  
   209  	conn, err := lifeTest(c, stub, apiagent.Life("zombie"), connect)
   210  	c.Check(conn, gc.IsNil)
   211  	c.Check(err, gc.ErrorMatches, `unknown life value "zombie"`)
   212  	stub.CheckCalls(c, []testing.StubCall{{
   213  		FuncName: "Life",
   214  		Args:     []interface{}{entity},
   215  	}, {
   216  		FuncName: "Close",
   217  	}})
   218  }
   219  
   220  func (*ScaryConnectSuite) TestChangePasswordConfigError(c *gc.C) {
   221  	// "random" failure case
   222  	stub, err := checkChangePassword(c, nil, errors.New("zap"))
   223  	c.Check(err, gc.ErrorMatches, "zap")
   224  	stub.CheckCallNames(c,
   225  		"Life", "ChangeConfig",
   226  		"Close",
   227  	)
   228  }
   229  
   230  func (*ScaryConnectSuite) TestChangePasswordRemoteError(c *gc.C) {
   231  	// "random" failure case
   232  	stub, err := checkChangePassword(c,
   233  		nil, nil, nil, nil, errors.New("pow"),
   234  	)
   235  	c.Check(err, gc.ErrorMatches, "pow")
   236  	stub.CheckCallNames(c,
   237  		"Life", "ChangeConfig",
   238  		// Be careful, these are two different SetPassword receivers.
   239  		"SetPassword", "SetOldPassword", "SetPassword",
   240  		"Close",
   241  	)
   242  	checkSaneChange(c, stub.Calls()[2:5])
   243  }
   244  
   245  func (*ScaryConnectSuite) TestChangePasswordRemoteDenied(c *gc.C) {
   246  	// permanent failure case
   247  	stub, err := checkChangePassword(c,
   248  		nil, nil, nil, nil, apiagent.ErrDenied,
   249  	)
   250  	c.Check(err, gc.Equals, apicaller.ErrConnectImpossible)
   251  	stub.CheckCallNames(c,
   252  		"Life", "ChangeConfig",
   253  		// Be careful, these are two different SetPassword receivers.
   254  		"SetPassword", "SetOldPassword", "SetPassword",
   255  		"Close",
   256  	)
   257  	checkSaneChange(c, stub.Calls()[2:5])
   258  }
   259  
   260  func (*ScaryConnectSuite) TestChangePasswordSuccess(c *gc.C) {
   261  	// retry-please failure case
   262  	stub, err := checkChangePassword(c)
   263  	c.Check(err, gc.Equals, apicaller.ErrChangedPassword)
   264  	stub.CheckCallNames(c,
   265  		"Life", "ChangeConfig",
   266  		// Be careful, these are two different SetPassword receivers.
   267  		"SetPassword", "SetOldPassword", "SetPassword",
   268  		"Close",
   269  	)
   270  	checkSaneChange(c, stub.Calls()[2:5])
   271  }
   272  
   273  func checkChangePassword(c *gc.C, errs ...error) (*testing.Stub, error) {
   274  	// We prepend the unauth/success pair that triggers password
   275  	// change, and consume them in apiOpen below...
   276  	errUnauth := &params.Error{Code: params.CodeUnauthorized}
   277  	allErrs := append([]error{errUnauth, nil}, errs...)
   278  
   279  	stub := &testing.Stub{}
   280  	stub.SetErrors(allErrs...)
   281  	expectConn := &mockConn{stub: stub}
   282  	apiOpen := func(info *api.Info, opts api.DialOpts) (api.Connection, error) {
   283  		// ...but we *don't* record the calls themselves; they
   284  		// are tested plenty elsewhere, and hiding them makes
   285  		// client code simpler.
   286  		if err := stub.NextErr(); err != nil {
   287  			return nil, err
   288  		}
   289  		return expectConn, nil
   290  	}
   291  
   292  	entity := names.NewServiceTag("omg")
   293  	connect := func() (api.Connection, error) {
   294  		return apicaller.ScaryConnect(&mockAgent{
   295  			stub:   stub,
   296  			model:  coretesting.ModelTag,
   297  			entity: entity,
   298  		}, apiOpen)
   299  	}
   300  
   301  	conn, err := lifeTest(c, stub, apiagent.Alive, connect)
   302  	c.Check(conn, gc.IsNil)
   303  	return stub, err
   304  }
   305  
   306  func checkSaneChange(c *gc.C, calls []testing.StubCall) {
   307  	c.Assert(calls, gc.HasLen, 3)
   308  	localSet := calls[0]
   309  	localSetOld := calls[1]
   310  	remoteSet := calls[2]
   311  	chosePassword := localSet.Args[0].(string)
   312  	switch chosePassword {
   313  	case "", "new", "old":
   314  		c.Fatalf("very bad new password: %q", chosePassword)
   315  	}
   316  
   317  	c.Check(localSet, jc.DeepEquals, testing.StubCall{
   318  		FuncName: "SetPassword",
   319  		Args:     []interface{}{chosePassword},
   320  	})
   321  	c.Check(localSetOld, jc.DeepEquals, testing.StubCall{
   322  		FuncName: "SetOldPassword",
   323  		Args:     []interface{}{"old"},
   324  	})
   325  	c.Check(remoteSet, jc.DeepEquals, testing.StubCall{
   326  		FuncName: "SetPassword",
   327  		Args:     []interface{}{names.NewServiceTag("omg"), chosePassword},
   328  	})
   329  }