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