github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/apiserver/root_test.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package apiserver_test
     5  
     6  import (
     7  	"fmt"
     8  	"reflect"
     9  	"sync"
    10  	"time"
    11  
    12  	jujutesting "github.com/juju/testing"
    13  	jc "github.com/juju/testing/checkers"
    14  	gc "gopkg.in/check.v1"
    15  	"gopkg.in/juju/names.v2"
    16  
    17  	"github.com/juju/juju/apiserver"
    18  	"github.com/juju/juju/apiserver/common"
    19  	"github.com/juju/juju/apiserver/facade"
    20  	"github.com/juju/juju/rpc/rpcreflect"
    21  	"github.com/juju/juju/state"
    22  	"github.com/juju/juju/testing"
    23  )
    24  
    25  type pingSuite struct {
    26  	testing.BaseSuite
    27  }
    28  
    29  var _ = gc.Suite(&pingSuite{})
    30  
    31  func (r *pingSuite) TestPingTimeout(c *gc.C) {
    32  	triggered := make(chan struct{})
    33  	action := func() {
    34  		close(triggered)
    35  	}
    36  	clock := jujutesting.NewClock(time.Now())
    37  	timeout := apiserver.NewPingTimeout(action, clock, 50*time.Millisecond)
    38  	for i := 0; i < 2; i++ {
    39  		waitAlarm(c, clock)
    40  		clock.Advance(10 * time.Millisecond)
    41  		timeout.Ping()
    42  	}
    43  
    44  	waitAlarm(c, clock)
    45  	clock.Advance(49 * time.Millisecond)
    46  	select {
    47  	case <-triggered:
    48  		c.Fatalf("action triggered early")
    49  	case <-time.After(testing.ShortWait):
    50  	}
    51  
    52  	clock.Advance(time.Millisecond)
    53  	select {
    54  	case <-triggered:
    55  	case <-time.After(testing.LongWait):
    56  		c.Fatalf("action never triggered")
    57  	}
    58  }
    59  
    60  func (r *pingSuite) TestPingTimeoutStopped(c *gc.C) {
    61  	triggered := make(chan struct{})
    62  	action := func() {
    63  		close(triggered)
    64  	}
    65  	clock := jujutesting.NewClock(time.Now())
    66  	timeout := apiserver.NewPingTimeout(action, clock, 20*time.Millisecond)
    67  
    68  	waitAlarm(c, clock)
    69  	timeout.Stop()
    70  	clock.Advance(time.Hour)
    71  
    72  	// The action should never trigger
    73  	select {
    74  	case <-triggered:
    75  		c.Fatalf("action triggered after Stop()")
    76  	case <-time.After(testing.ShortWait):
    77  	}
    78  }
    79  
    80  func waitAlarm(c *gc.C, clock *jujutesting.Clock) {
    81  	select {
    82  	case <-time.After(testing.LongWait):
    83  		c.Fatalf("alarm never set")
    84  	case <-clock.Alarms():
    85  	}
    86  }
    87  
    88  type errRootSuite struct {
    89  	testing.BaseSuite
    90  }
    91  
    92  var _ = gc.Suite(&errRootSuite{})
    93  
    94  func (s *errRootSuite) TestErrorRoot(c *gc.C) {
    95  	origErr := fmt.Errorf("my custom error")
    96  	errRoot := apiserver.NewErrRoot(origErr)
    97  	st, err := errRoot.FindMethod("", 0, "")
    98  	c.Check(st, gc.IsNil)
    99  	c.Check(err, gc.Equals, origErr)
   100  }
   101  
   102  type testingType struct{}
   103  
   104  func (testingType) Exposed() error {
   105  	return fmt.Errorf("Exposed was bogus")
   106  }
   107  
   108  type badType struct{}
   109  
   110  func (badType) Exposed() error {
   111  	return fmt.Errorf("badType.Exposed was not to be exposed")
   112  }
   113  
   114  type rootSuite struct {
   115  	testing.BaseSuite
   116  }
   117  
   118  var _ = gc.Suite(&rootSuite{})
   119  
   120  func (r *rootSuite) TestFindMethodUnknownFacade(c *gc.C) {
   121  	root := apiserver.TestingAPIRoot(nil)
   122  	caller, err := root.FindMethod("unknown-testing-facade", 0, "Method")
   123  	c.Check(caller, gc.IsNil)
   124  	c.Check(err, gc.FitsTypeOf, (*rpcreflect.CallNotImplementedError)(nil))
   125  	c.Check(err, gc.ErrorMatches, `unknown object type "unknown-testing-facade"`)
   126  }
   127  
   128  func (r *rootSuite) TestFindMethodUnknownVersion(c *gc.C) {
   129  	srvRoot := apiserver.TestingAPIRoot(nil)
   130  	defer common.Facades.Discard("my-testing-facade", 0)
   131  	myGoodFacade := func(
   132  		*state.State, facade.Resources, facade.Authorizer,
   133  	) (
   134  		*testingType, error,
   135  	) {
   136  		return &testingType{}, nil
   137  	}
   138  	common.RegisterStandardFacade("my-testing-facade", 0, myGoodFacade)
   139  	caller, err := srvRoot.FindMethod("my-testing-facade", 1, "Exposed")
   140  	c.Check(caller, gc.IsNil)
   141  	c.Check(err, gc.FitsTypeOf, (*rpcreflect.CallNotImplementedError)(nil))
   142  	c.Check(err, gc.ErrorMatches, `unknown version \(1\) of interface "my-testing-facade"`)
   143  }
   144  
   145  func (r *rootSuite) TestFindMethodEnsuresTypeMatch(c *gc.C) {
   146  	srvRoot := apiserver.TestingAPIRoot(nil)
   147  	defer common.Facades.Discard("my-testing-facade", 0)
   148  	defer common.Facades.Discard("my-testing-facade", 1)
   149  	defer common.Facades.Discard("my-testing-facade", 2)
   150  	myBadFacade := func(facade.Context) (facade.Facade, error) {
   151  		return &badType{}, nil
   152  	}
   153  	myGoodFacade := func(facade.Context) (facade.Facade, error) {
   154  		return &testingType{}, nil
   155  	}
   156  	myErrFacade := func(context facade.Context) (facade.Facade, error) {
   157  		return nil, fmt.Errorf("you shall not pass")
   158  	}
   159  	expectedType := reflect.TypeOf((*testingType)(nil))
   160  	common.RegisterFacade("my-testing-facade", 0, myBadFacade, expectedType)
   161  	common.RegisterFacade("my-testing-facade", 1, myGoodFacade, expectedType)
   162  	common.RegisterFacade("my-testing-facade", 2, myErrFacade, expectedType)
   163  	// Now, myGoodFacade returns the right type, so calling it should work
   164  	// fine
   165  	caller, err := srvRoot.FindMethod("my-testing-facade", 1, "Exposed")
   166  	c.Assert(err, jc.ErrorIsNil)
   167  	_, err = caller.Call("", reflect.Value{})
   168  	c.Check(err, gc.ErrorMatches, "Exposed was bogus")
   169  	// However, myBadFacade returns the wrong type, so trying to access it
   170  	// should create an error
   171  	caller, err = srvRoot.FindMethod("my-testing-facade", 0, "Exposed")
   172  	c.Assert(err, jc.ErrorIsNil)
   173  	_, err = caller.Call("", reflect.Value{})
   174  	c.Check(err, gc.ErrorMatches,
   175  		`internal error, my-testing-facade\(0\) claimed to return \*apiserver_test.testingType but returned \*apiserver_test.badType`)
   176  	// myErrFacade had the permissions change, so calling it returns an
   177  	// error, but that shouldn't trigger the type checking code.
   178  	caller, err = srvRoot.FindMethod("my-testing-facade", 2, "Exposed")
   179  	c.Assert(err, jc.ErrorIsNil)
   180  	res, err := caller.Call("", reflect.Value{})
   181  	c.Check(err, gc.ErrorMatches, `you shall not pass`)
   182  	c.Check(res.IsValid(), jc.IsFalse)
   183  }
   184  
   185  type stringVar struct {
   186  	Val string
   187  }
   188  
   189  type countingType struct {
   190  	count int64
   191  	id    string
   192  }
   193  
   194  func (ct *countingType) Count() stringVar {
   195  	return stringVar{fmt.Sprintf("%s%d", ct.id, ct.count)}
   196  }
   197  
   198  func (ct *countingType) AltCount() stringVar {
   199  	return stringVar{fmt.Sprintf("ALT-%s%d", ct.id, ct.count)}
   200  }
   201  
   202  func assertCallResult(c *gc.C, caller rpcreflect.MethodCaller, id string, expected string) {
   203  	v, err := caller.Call(id, reflect.Value{})
   204  	c.Assert(err, jc.ErrorIsNil)
   205  	c.Check(v.Interface(), gc.Equals, stringVar{expected})
   206  }
   207  
   208  func (r *rootSuite) TestFindMethodCachesFacades(c *gc.C) {
   209  	srvRoot := apiserver.TestingAPIRoot(nil)
   210  	defer common.Facades.Discard("my-counting-facade", 0)
   211  	defer common.Facades.Discard("my-counting-facade", 1)
   212  	var count int64
   213  	newCounter := func(
   214  		*state.State, facade.Resources, facade.Authorizer,
   215  	) (
   216  		*countingType, error,
   217  	) {
   218  		count += 1
   219  		return &countingType{count: count, id: ""}, nil
   220  	}
   221  	common.RegisterStandardFacade("my-counting-facade", 0, newCounter)
   222  	common.RegisterStandardFacade("my-counting-facade", 1, newCounter)
   223  	// The first time we call FindMethod, it should lookup a facade, and
   224  	// request a new object.
   225  	caller, err := srvRoot.FindMethod("my-counting-facade", 0, "Count")
   226  	c.Assert(err, jc.ErrorIsNil)
   227  	assertCallResult(c, caller, "", "1")
   228  	// The second time we ask for a method on the same facade, it should
   229  	// reuse that object, rather than creating another instance
   230  	caller, err = srvRoot.FindMethod("my-counting-facade", 0, "AltCount")
   231  	c.Assert(err, jc.ErrorIsNil)
   232  	assertCallResult(c, caller, "", "ALT-1")
   233  	// But when we ask for a different version, we should get a new
   234  	// instance
   235  	caller, err = srvRoot.FindMethod("my-counting-facade", 1, "Count")
   236  	c.Assert(err, jc.ErrorIsNil)
   237  	assertCallResult(c, caller, "", "2")
   238  	// But it, too, should be cached
   239  	caller, err = srvRoot.FindMethod("my-counting-facade", 1, "AltCount")
   240  	c.Assert(err, jc.ErrorIsNil)
   241  	assertCallResult(c, caller, "", "ALT-2")
   242  }
   243  
   244  func (r *rootSuite) TestFindMethodCachesFacadesWithId(c *gc.C) {
   245  	srvRoot := apiserver.TestingAPIRoot(nil)
   246  	defer common.Facades.Discard("my-counting-facade", 0)
   247  	var count int64
   248  	// like newCounter, but also tracks the "id" that was requested for
   249  	// this counter
   250  	newIdCounter := func(context facade.Context) (facade.Facade, error) {
   251  		count += 1
   252  		return &countingType{count: count, id: context.ID()}, nil
   253  	}
   254  	reflectType := reflect.TypeOf((*countingType)(nil))
   255  	common.RegisterFacade("my-counting-facade", 0, newIdCounter, reflectType)
   256  	// The first time we call FindMethod, it should lookup a facade, and
   257  	// request a new object.
   258  	caller, err := srvRoot.FindMethod("my-counting-facade", 0, "Count")
   259  	c.Assert(err, jc.ErrorIsNil)
   260  	assertCallResult(c, caller, "orig-id", "orig-id1")
   261  	// However, if we place another call for a different Id, it should grab
   262  	// a new object
   263  	assertCallResult(c, caller, "alt-id", "alt-id2")
   264  	// Asking for the original object gives us the cached value
   265  	assertCallResult(c, caller, "orig-id", "orig-id1")
   266  	// Asking for the original object gives us the cached value
   267  	assertCallResult(c, caller, "alt-id", "alt-id2")
   268  	// We get the same results asking for the other method
   269  	caller, err = srvRoot.FindMethod("my-counting-facade", 0, "AltCount")
   270  	c.Assert(err, jc.ErrorIsNil)
   271  	assertCallResult(c, caller, "orig-id", "ALT-orig-id1")
   272  	assertCallResult(c, caller, "alt-id", "ALT-alt-id2")
   273  	assertCallResult(c, caller, "third-id", "ALT-third-id3")
   274  }
   275  
   276  func (r *rootSuite) TestFindMethodCacheRaceSafe(c *gc.C) {
   277  	srvRoot := apiserver.TestingAPIRoot(nil)
   278  	defer common.Facades.Discard("my-counting-facade", 0)
   279  	var count int64
   280  	newIdCounter := func(context facade.Context) (facade.Facade, error) {
   281  		count += 1
   282  		return &countingType{count: count, id: context.ID()}, nil
   283  	}
   284  	reflectType := reflect.TypeOf((*countingType)(nil))
   285  	common.RegisterFacade("my-counting-facade", 0, newIdCounter, reflectType)
   286  	caller, err := srvRoot.FindMethod("my-counting-facade", 0, "Count")
   287  	c.Assert(err, jc.ErrorIsNil)
   288  	// This is designed to trigger the race detector
   289  	var wg sync.WaitGroup
   290  	wg.Add(4)
   291  	go func() { caller.Call("first", reflect.Value{}); wg.Done() }()
   292  	go func() { caller.Call("second", reflect.Value{}); wg.Done() }()
   293  	go func() { caller.Call("first", reflect.Value{}); wg.Done() }()
   294  	go func() { caller.Call("second", reflect.Value{}); wg.Done() }()
   295  	wg.Wait()
   296  	// Once we're done, we should have only instantiated 2 different
   297  	// objects. If we pass a different Id, we should be at 3 total count.
   298  	assertCallResult(c, caller, "third", "third3")
   299  }
   300  
   301  type smallInterface interface {
   302  	OneMethod() stringVar
   303  }
   304  
   305  type firstImpl struct {
   306  }
   307  
   308  func (*firstImpl) OneMethod() stringVar {
   309  	return stringVar{"first"}
   310  }
   311  
   312  type secondImpl struct {
   313  }
   314  
   315  func (*secondImpl) AMethod() stringVar {
   316  	return stringVar{"A"}
   317  }
   318  
   319  func (*secondImpl) ZMethod() stringVar {
   320  	return stringVar{"Z"}
   321  }
   322  
   323  func (*secondImpl) OneMethod() stringVar {
   324  	return stringVar{"second"}
   325  }
   326  
   327  func (r *rootSuite) TestFindMethodHandlesInterfaceTypes(c *gc.C) {
   328  	srvRoot := apiserver.TestingAPIRoot(nil)
   329  	defer common.Facades.Discard("my-interface-facade", 0)
   330  	defer common.Facades.Discard("my-interface-facade", 1)
   331  	common.RegisterStandardFacade("my-interface-facade", 0, func(
   332  		*state.State, facade.Resources, facade.Authorizer,
   333  	) (
   334  		smallInterface, error,
   335  	) {
   336  		return &firstImpl{}, nil
   337  	})
   338  	common.RegisterStandardFacade("my-interface-facade", 1, func(
   339  		*state.State, facade.Resources, facade.Authorizer,
   340  	) (
   341  		smallInterface, error,
   342  	) {
   343  		return &secondImpl{}, nil
   344  	})
   345  	caller, err := srvRoot.FindMethod("my-interface-facade", 0, "OneMethod")
   346  	c.Assert(err, jc.ErrorIsNil)
   347  	assertCallResult(c, caller, "", "first")
   348  	caller2, err := srvRoot.FindMethod("my-interface-facade", 1, "OneMethod")
   349  	c.Assert(err, jc.ErrorIsNil)
   350  	assertCallResult(c, caller2, "", "second")
   351  	// We should *not* be able to see AMethod or ZMethod
   352  	caller, err = srvRoot.FindMethod("my-interface-facade", 1, "AMethod")
   353  	c.Check(err, gc.FitsTypeOf, (*rpcreflect.CallNotImplementedError)(nil))
   354  	c.Check(err, gc.ErrorMatches,
   355  		`no such request - method my-interface-facade\(1\)\.AMethod is not implemented`)
   356  	c.Check(caller, gc.IsNil)
   357  	caller, err = srvRoot.FindMethod("my-interface-facade", 1, "ZMethod")
   358  	c.Check(err, gc.FitsTypeOf, (*rpcreflect.CallNotImplementedError)(nil))
   359  	c.Check(err, gc.ErrorMatches,
   360  		`no such request - method my-interface-facade\(1\)\.ZMethod is not implemented`)
   361  	c.Check(caller, gc.IsNil)
   362  }
   363  
   364  func (r *rootSuite) TestDescribeFacades(c *gc.C) {
   365  	facades := apiserver.DescribeFacades()
   366  	c.Check(facades, gc.Not(gc.HasLen), 0)
   367  	// As a sanity check, we should see that we have a Client v0 available
   368  	asMap := make(map[string][]int, len(facades))
   369  	for _, facade := range facades {
   370  		asMap[facade.Name] = facade.Versions
   371  	}
   372  	clientVersions := asMap["Client"]
   373  	c.Assert(len(clientVersions), jc.GreaterThan, 0)
   374  	c.Check(clientVersions[0], gc.Equals, 1)
   375  }
   376  
   377  type stubStateEntity struct{ tag names.Tag }
   378  
   379  func (e *stubStateEntity) Tag() names.Tag { return e.tag }
   380  
   381  func (r *rootSuite) TestAuthOwner(c *gc.C) {
   382  
   383  	tag, err := names.ParseUnitTag("unit-postgresql-0")
   384  	if err != nil {
   385  		c.Errorf("error parsing unit tag for test: %s", err)
   386  	}
   387  
   388  	entity := &stubStateEntity{tag}
   389  
   390  	apiHandler := apiserver.APIHandlerWithEntity(entity)
   391  	authorized := apiHandler.AuthOwner(tag)
   392  
   393  	c.Check(authorized, jc.IsTrue)
   394  
   395  	incorrectTag, err := names.ParseUnitTag("unit-mysql-0")
   396  	if err != nil {
   397  		c.Errorf("error parsing unit tag for test: %s", err)
   398  	}
   399  
   400  	authorized = apiHandler.AuthOwner(incorrectTag)
   401  
   402  	c.Check(authorized, jc.IsFalse)
   403  }