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 }