github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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/testing"
    10  	jc "github.com/juju/testing/checkers"
    11  	gc "gopkg.in/check.v1"
    12  	"gopkg.in/juju/names.v2"
    13  
    14  	"github.com/juju/juju/api"
    15  	apiagent "github.com/juju/juju/api/agent"
    16  	"github.com/juju/juju/apiserver/common"
    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.NewApplicationTag("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) TestEntityDead(c *gc.C) {
    74  	// permanent failure case
    75  	stub := &testing.Stub{}
    76  	expectConn := &mockConn{stub: stub}
    77  	apiOpen := func(info *api.Info, opts api.DialOpts) (api.Connection, error) {
    78  		return expectConn, nil
    79  	}
    80  
    81  	entity := names.NewApplicationTag("omg")
    82  	connect := func() (api.Connection, error) {
    83  		return apicaller.ScaryConnect(&mockAgent{
    84  			stub:   stub,
    85  			model:  coretesting.ModelTag,
    86  			entity: entity,
    87  		}, apiOpen)
    88  	}
    89  
    90  	conn, err := lifeTest(c, stub, apiagent.Dead, connect)
    91  	c.Check(conn, gc.IsNil)
    92  	c.Check(err, gc.Equals, apicaller.ErrConnectImpossible)
    93  	stub.CheckCalls(c, []testing.StubCall{{
    94  		FuncName: "Life",
    95  		Args:     []interface{}{entity},
    96  	}, {
    97  		FuncName: "Close",
    98  	}})
    99  }
   100  
   101  func (*ScaryConnectSuite) TestEntityDenied(c *gc.C) {
   102  	// permanent failure case
   103  	stub := &testing.Stub{}
   104  	stub.SetErrors(apiagent.ErrDenied)
   105  	expectConn := &mockConn{stub: stub}
   106  	apiOpen := func(info *api.Info, opts api.DialOpts) (api.Connection, error) {
   107  		return expectConn, nil
   108  	}
   109  
   110  	entity := names.NewApplicationTag("omg")
   111  	connect := func() (api.Connection, error) {
   112  		return apicaller.ScaryConnect(&mockAgent{
   113  			stub:   stub,
   114  			model:  coretesting.ModelTag,
   115  			entity: entity,
   116  		}, apiOpen)
   117  	}
   118  
   119  	conn, err := lifeTest(c, stub, apiagent.Dead, connect)
   120  	c.Check(conn, gc.IsNil)
   121  	c.Check(err, gc.Equals, apicaller.ErrConnectImpossible)
   122  	stub.CheckCalls(c, []testing.StubCall{{
   123  		FuncName: "Life",
   124  		Args:     []interface{}{entity},
   125  	}, {
   126  		FuncName: "Close",
   127  	}})
   128  }
   129  
   130  func (*ScaryConnectSuite) TestEntityUnknownLife(c *gc.C) {
   131  	// "random" failure case
   132  	stub := &testing.Stub{}
   133  	expectConn := &mockConn{stub: stub}
   134  	apiOpen := func(info *api.Info, opts api.DialOpts) (api.Connection, error) {
   135  		return expectConn, nil
   136  	}
   137  
   138  	entity := names.NewApplicationTag("omg")
   139  	connect := func() (api.Connection, error) {
   140  		return apicaller.ScaryConnect(&mockAgent{
   141  			stub:   stub,
   142  			model:  coretesting.ModelTag,
   143  			entity: entity,
   144  		}, apiOpen)
   145  	}
   146  
   147  	conn, err := lifeTest(c, stub, apiagent.Life("zombie"), connect)
   148  	c.Check(conn, gc.IsNil)
   149  	c.Check(err, gc.ErrorMatches, `unknown life value "zombie"`)
   150  	stub.CheckCalls(c, []testing.StubCall{{
   151  		FuncName: "Life",
   152  		Args:     []interface{}{entity},
   153  	}, {
   154  		FuncName: "Close",
   155  	}})
   156  }
   157  
   158  func (*ScaryConnectSuite) TestChangePasswordConfigError(c *gc.C) {
   159  	// "random" failure case
   160  	stub := createUnauthorisedStub(nil, errors.New("zap"))
   161  	err := checkChangePassword(c, stub)
   162  	c.Check(err, gc.ErrorMatches, "zap")
   163  	stub.CheckCallNames(c,
   164  		"Life", "ChangeConfig",
   165  		"Close",
   166  	)
   167  }
   168  
   169  func (*ScaryConnectSuite) TestChangePasswordRemoteError(c *gc.C) {
   170  	// "random" failure case
   171  	stub := createUnauthorisedStub(nil, nil, nil, nil, errors.New("pow"))
   172  	err := checkChangePassword(c, stub)
   173  	c.Check(err, gc.ErrorMatches, "pow")
   174  	stub.CheckCallNames(c,
   175  		"Life", "ChangeConfig",
   176  		// Be careful, these are two different SetPassword receivers.
   177  		"SetPassword", "SetOldPassword", "SetPassword",
   178  		"Close",
   179  	)
   180  	checkSaneChange(c, stub.Calls()[2:5])
   181  }
   182  
   183  func (*ScaryConnectSuite) TestChangePasswordRemoteDenied(c *gc.C) {
   184  	// permanent failure case
   185  	stub := createUnauthorisedStub(nil, nil, nil, nil, apiagent.ErrDenied)
   186  	err := checkChangePassword(c, stub)
   187  	c.Check(err, gc.Equals, apicaller.ErrConnectImpossible)
   188  	stub.CheckCallNames(c,
   189  		"Life", "ChangeConfig",
   190  		// Be careful, these are two different SetPassword receivers.
   191  		"SetPassword", "SetOldPassword", "SetPassword",
   192  		"Close",
   193  	)
   194  	checkSaneChange(c, stub.Calls()[2:5])
   195  }
   196  
   197  func (s *ScaryConnectSuite) TestChangePasswordSuccessAfterUnauthorisedError(c *gc.C) {
   198  	// This will try to login with old password if current one fails.
   199  	stub := createUnauthorisedStub()
   200  	s.assertChangePasswordSuccess(c, stub)
   201  }
   202  
   203  func (s *ScaryConnectSuite) TestChangePasswordSuccessAfterBadCurrentPasswordError(c *gc.C) {
   204  	// This will try to login with old password if current one fails.
   205  	stub := createPasswordCheckStub(common.ErrBadCreds)
   206  	s.assertChangePasswordSuccess(c, stub)
   207  }
   208  
   209  func (*ScaryConnectSuite) assertChangePasswordSuccess(c *gc.C, stub *testing.Stub) {
   210  	err := checkChangePassword(c, stub)
   211  	c.Check(err, gc.Equals, apicaller.ErrChangedPassword)
   212  	stub.CheckCallNames(c,
   213  		"Life", "ChangeConfig",
   214  		// Be careful, these are two different SetPassword receivers.
   215  		"SetPassword", "SetOldPassword", "SetPassword",
   216  		"Close",
   217  	)
   218  	checkSaneChange(c, stub.Calls()[2:5])
   219  }
   220  
   221  func createUnauthorisedStub(errs ...error) *testing.Stub {
   222  	return createPasswordCheckStub(&params.Error{Code: params.CodeUnauthorized}, errs...)
   223  }
   224  
   225  func createPasswordCheckStub(currentPwdLoginErr error, errs ...error) *testing.Stub {
   226  	allErrs := append([]error{currentPwdLoginErr, nil}, errs...)
   227  
   228  	stub := &testing.Stub{}
   229  	stub.SetErrors(allErrs...)
   230  	return stub
   231  }
   232  
   233  func checkChangePassword(c *gc.C, stub *testing.Stub) error {
   234  	// We prepend the unauth/success pair that triggers password
   235  	// change, and consume them in apiOpen below...
   236  	//errUnauth := &params.Error{Code: params.CodeUnauthorized}
   237  	//allErrs := append([]error{errUnauth, nil}, errs...)
   238  	//
   239  	//stub := &testing.Stub{}
   240  	//stub.SetErrors(allErrs...)
   241  	expectConn := &mockConn{stub: stub}
   242  	apiOpen := func(info *api.Info, opts api.DialOpts) (api.Connection, error) {
   243  		// ...but we *don't* record the calls themselves; they
   244  		// are tested plenty elsewhere, and hiding them makes
   245  		// client code simpler.
   246  		if err := stub.NextErr(); err != nil {
   247  			return nil, err
   248  		}
   249  		return expectConn, nil
   250  	}
   251  
   252  	entity := names.NewApplicationTag("omg")
   253  	connect := func() (api.Connection, error) {
   254  		return apicaller.ScaryConnect(&mockAgent{
   255  			stub:   stub,
   256  			model:  coretesting.ModelTag,
   257  			entity: entity,
   258  		}, apiOpen)
   259  	}
   260  
   261  	conn, err := lifeTest(c, stub, apiagent.Alive, connect)
   262  	c.Check(conn, gc.IsNil)
   263  	return err
   264  }
   265  
   266  func checkSaneChange(c *gc.C, calls []testing.StubCall) {
   267  	c.Assert(calls, gc.HasLen, 3)
   268  	localSet := calls[0]
   269  	localSetOld := calls[1]
   270  	remoteSet := calls[2]
   271  	chosePassword := localSet.Args[0].(string)
   272  	switch chosePassword {
   273  	case "", "new", "old":
   274  		c.Fatalf("very bad new password: %q", chosePassword)
   275  	}
   276  
   277  	c.Check(localSet, jc.DeepEquals, testing.StubCall{
   278  		FuncName: "SetPassword",
   279  		Args:     []interface{}{chosePassword},
   280  	})
   281  	c.Check(localSetOld, jc.DeepEquals, testing.StubCall{
   282  		FuncName: "SetOldPassword",
   283  		Args:     []interface{}{"old"},
   284  	})
   285  	c.Check(remoteSet, jc.DeepEquals, testing.StubCall{
   286  		FuncName: "SetPassword",
   287  		Args:     []interface{}{names.NewApplicationTag("omg"), chosePassword},
   288  	})
   289  }