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 }