github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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 "github.com/juju/names" 13 jc "github.com/juju/testing/checkers" 14 gc "gopkg.in/check.v1" 15 16 "github.com/juju/juju/apiserver" 17 "github.com/juju/juju/apiserver/common" 18 "github.com/juju/juju/rpc/rpcreflect" 19 "github.com/juju/juju/state" 20 "github.com/juju/juju/testing" 21 ) 22 23 type rootSuite struct { 24 testing.BaseSuite 25 } 26 27 var _ = gc.Suite(&rootSuite{}) 28 29 var allowedDiscardedMethods = []string{ 30 "AuthClient", 31 "AuthModelManager", 32 "AuthMachineAgent", 33 "AuthOwner", 34 "AuthUnitAgent", 35 "FindMethod", 36 "GetAuthEntity", 37 "GetAuthTag", 38 } 39 40 func (r *rootSuite) TestPingTimeout(c *gc.C) { 41 closedc := make(chan time.Time, 1) 42 action := func() { 43 closedc <- time.Now() 44 } 45 timeout := apiserver.NewPingTimeout(action, 50*time.Millisecond) 46 for i := 0; i < 2; i++ { 47 time.Sleep(10 * time.Millisecond) 48 timeout.Ping() 49 } 50 // Expect action to be executed about 50ms after last ping. 51 broken := time.Now() 52 var closed time.Time 53 select { 54 case closed = <-closedc: 55 case <-time.After(testing.LongWait): 56 c.Fatalf("action never executed") 57 } 58 closeDiff := closed.Sub(broken) / time.Millisecond 59 c.Assert(50 <= closeDiff && closeDiff <= 100, jc.IsTrue) 60 } 61 62 func (r *rootSuite) TestPingTimeoutStopped(c *gc.C) { 63 closedc := make(chan time.Time, 1) 64 action := func() { 65 closedc <- time.Now() 66 } 67 timeout := apiserver.NewPingTimeout(action, 20*time.Millisecond) 68 timeout.Ping() 69 timeout.Stop() 70 // The action should never trigger 71 select { 72 case <-closedc: 73 c.Fatalf("action triggered after Stop()") 74 case <-time.After(testing.ShortWait): 75 } 76 } 77 78 type errRootSuite struct { 79 testing.BaseSuite 80 } 81 82 var _ = gc.Suite(&errRootSuite{}) 83 84 func (s *errRootSuite) TestErrorRoot(c *gc.C) { 85 origErr := fmt.Errorf("my custom error") 86 errRoot := apiserver.NewErrRoot(origErr) 87 st, err := errRoot.FindMethod("", 0, "") 88 c.Check(st, gc.IsNil) 89 c.Check(err, gc.Equals, origErr) 90 } 91 92 type testingType struct{} 93 94 func (testingType) Exposed() error { 95 return fmt.Errorf("Exposed was bogus") 96 } 97 98 type badType struct{} 99 100 func (badType) Exposed() error { 101 return fmt.Errorf("badType.Exposed was not to be exposed") 102 } 103 104 func (r *rootSuite) TestFindMethodUnknownFacade(c *gc.C) { 105 root := apiserver.TestingApiRoot(nil) 106 caller, err := root.FindMethod("unknown-testing-facade", 0, "Method") 107 c.Check(caller, gc.IsNil) 108 c.Check(err, gc.FitsTypeOf, (*rpcreflect.CallNotImplementedError)(nil)) 109 c.Check(err, gc.ErrorMatches, `unknown object type "unknown-testing-facade"`) 110 } 111 112 func (r *rootSuite) TestFindMethodUnknownVersion(c *gc.C) { 113 srvRoot := apiserver.TestingApiRoot(nil) 114 defer common.Facades.Discard("my-testing-facade", 0) 115 myGoodFacade := func( 116 *state.State, *common.Resources, common.Authorizer, 117 ) ( 118 *testingType, error, 119 ) { 120 return &testingType{}, nil 121 } 122 common.RegisterStandardFacade("my-testing-facade", 0, myGoodFacade) 123 caller, err := srvRoot.FindMethod("my-testing-facade", 1, "Exposed") 124 c.Check(caller, gc.IsNil) 125 c.Check(err, gc.FitsTypeOf, (*rpcreflect.CallNotImplementedError)(nil)) 126 c.Check(err, gc.ErrorMatches, `unknown version \(1\) of interface "my-testing-facade"`) 127 } 128 129 func (r *rootSuite) TestFindMethodEnsuresTypeMatch(c *gc.C) { 130 srvRoot := apiserver.TestingApiRoot(nil) 131 defer common.Facades.Discard("my-testing-facade", 0) 132 defer common.Facades.Discard("my-testing-facade", 1) 133 defer common.Facades.Discard("my-testing-facade", 2) 134 myBadFacade := func( 135 *state.State, *common.Resources, common.Authorizer, string, 136 ) ( 137 interface{}, error, 138 ) { 139 return &badType{}, nil 140 } 141 myGoodFacade := func( 142 *state.State, *common.Resources, common.Authorizer, string, 143 ) ( 144 interface{}, error, 145 ) { 146 return &testingType{}, nil 147 } 148 myErrFacade := func( 149 *state.State, *common.Resources, common.Authorizer, string, 150 ) ( 151 interface{}, error, 152 ) { 153 return nil, fmt.Errorf("you shall not pass") 154 } 155 expectedType := reflect.TypeOf((*testingType)(nil)) 156 common.RegisterFacade("my-testing-facade", 0, myBadFacade, expectedType) 157 common.RegisterFacade("my-testing-facade", 1, myGoodFacade, expectedType) 158 common.RegisterFacade("my-testing-facade", 2, myErrFacade, expectedType) 159 // Now, myGoodFacade returns the right type, so calling it should work 160 // fine 161 caller, err := srvRoot.FindMethod("my-testing-facade", 1, "Exposed") 162 c.Assert(err, jc.ErrorIsNil) 163 _, err = caller.Call("", reflect.Value{}) 164 c.Check(err, gc.ErrorMatches, "Exposed was bogus") 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("", 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 // myErrFacade had the permissions change, so calling it returns an 173 // error, but that shouldn't trigger the type checking code. 174 caller, err = srvRoot.FindMethod("my-testing-facade", 2, "Exposed") 175 c.Assert(err, jc.ErrorIsNil) 176 res, err := caller.Call("", reflect.Value{}) 177 c.Check(err, gc.ErrorMatches, `you shall not pass`) 178 c.Check(res.IsValid(), jc.IsFalse) 179 } 180 181 type stringVar struct { 182 Val string 183 } 184 185 type countingType struct { 186 count int64 187 id string 188 } 189 190 func (ct *countingType) Count() stringVar { 191 return stringVar{fmt.Sprintf("%s%d", ct.id, ct.count)} 192 } 193 194 func (ct *countingType) AltCount() stringVar { 195 return stringVar{fmt.Sprintf("ALT-%s%d", ct.id, ct.count)} 196 } 197 198 func assertCallResult(c *gc.C, caller rpcreflect.MethodCaller, id string, expected string) { 199 v, err := caller.Call(id, reflect.Value{}) 200 c.Assert(err, jc.ErrorIsNil) 201 c.Check(v.Interface(), gc.Equals, stringVar{expected}) 202 } 203 204 func (r *rootSuite) TestFindMethodCachesFacades(c *gc.C) { 205 srvRoot := apiserver.TestingApiRoot(nil) 206 defer common.Facades.Discard("my-counting-facade", 0) 207 defer common.Facades.Discard("my-counting-facade", 1) 208 var count int64 209 newCounter := func( 210 *state.State, *common.Resources, common.Authorizer, 211 ) ( 212 *countingType, error, 213 ) { 214 count += 1 215 return &countingType{count: count, id: ""}, nil 216 } 217 common.RegisterStandardFacade("my-counting-facade", 0, newCounter) 218 common.RegisterStandardFacade("my-counting-facade", 1, newCounter) 219 // The first time we call FindMethod, it should lookup a facade, and 220 // request a new object. 221 caller, err := srvRoot.FindMethod("my-counting-facade", 0, "Count") 222 c.Assert(err, jc.ErrorIsNil) 223 assertCallResult(c, caller, "", "1") 224 // The second time we ask for a method on the same facade, it should 225 // reuse that object, rather than creating another instance 226 caller, err = srvRoot.FindMethod("my-counting-facade", 0, "AltCount") 227 c.Assert(err, jc.ErrorIsNil) 228 assertCallResult(c, caller, "", "ALT-1") 229 // But when we ask for a different version, we should get a new 230 // instance 231 caller, err = srvRoot.FindMethod("my-counting-facade", 1, "Count") 232 c.Assert(err, jc.ErrorIsNil) 233 assertCallResult(c, caller, "", "2") 234 // But it, too, should be cached 235 caller, err = srvRoot.FindMethod("my-counting-facade", 1, "AltCount") 236 c.Assert(err, jc.ErrorIsNil) 237 assertCallResult(c, caller, "", "ALT-2") 238 } 239 240 func (r *rootSuite) TestFindMethodCachesFacadesWithId(c *gc.C) { 241 srvRoot := apiserver.TestingApiRoot(nil) 242 defer common.Facades.Discard("my-counting-facade", 0) 243 var count int64 244 // like newCounter, but also tracks the "id" that was requested for 245 // this counter 246 newIdCounter := func( 247 _ *state.State, _ *common.Resources, _ common.Authorizer, id string, 248 ) (interface{}, error) { 249 count += 1 250 return &countingType{count: count, id: id}, nil 251 } 252 reflectType := reflect.TypeOf((*countingType)(nil)) 253 common.RegisterFacade("my-counting-facade", 0, newIdCounter, reflectType) 254 // The first time we call FindMethod, it should lookup a facade, and 255 // request a new object. 256 caller, err := srvRoot.FindMethod("my-counting-facade", 0, "Count") 257 c.Assert(err, jc.ErrorIsNil) 258 assertCallResult(c, caller, "orig-id", "orig-id1") 259 // However, if we place another call for a different Id, it should grab 260 // a new object 261 assertCallResult(c, caller, "alt-id", "alt-id2") 262 // Asking for the original object gives us the cached value 263 assertCallResult(c, caller, "orig-id", "orig-id1") 264 // Asking for the original object gives us the cached value 265 assertCallResult(c, caller, "alt-id", "alt-id2") 266 // We get the same results asking for the other method 267 caller, err = srvRoot.FindMethod("my-counting-facade", 0, "AltCount") 268 c.Assert(err, jc.ErrorIsNil) 269 assertCallResult(c, caller, "orig-id", "ALT-orig-id1") 270 assertCallResult(c, caller, "alt-id", "ALT-alt-id2") 271 assertCallResult(c, caller, "third-id", "ALT-third-id3") 272 } 273 274 func (r *rootSuite) TestFindMethodCacheRaceSafe(c *gc.C) { 275 srvRoot := apiserver.TestingApiRoot(nil) 276 defer common.Facades.Discard("my-counting-facade", 0) 277 var count int64 278 newIdCounter := func( 279 _ *state.State, _ *common.Resources, _ common.Authorizer, id string, 280 ) (interface{}, error) { 281 count += 1 282 return &countingType{count: count, id: 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, *common.Resources, common.Authorizer, 333 ) ( 334 smallInterface, error, 335 ) { 336 return &firstImpl{}, nil 337 }) 338 common.RegisterStandardFacade("my-interface-facade", 1, func( 339 *state.State, *common.Resources, common.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 }