Permalink
Switch branches/tags
Find file
Fetching contributors…
Cannot retrieve contributors at this time
229 lines (202 sloc) 7.5 KB
// Copyright 2014 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.
package common
import (
"fmt"
"reflect"
"runtime"
"github.com/juju/errors"
"gopkg.in/juju/names.v2"
"github.com/juju/juju/apiserver/facade"
"github.com/juju/juju/state"
)
// Facades is the registry that tracks all of the Facades that will be
// exposed in the API. It can be used to List/Get/Register facades.
//
// Most implementers of a facade will probably want to use
// RegisterStandardFacade rather than Facades.Register, as it provides
// much cleaner syntax and semantics for calling during init().
//
// Developers in a happy future will not want to use Facades at all,
// eschewing globals in favour of explicitly building apis and supplying
// them directly to an apiserver.
var Facades = &facade.Registry{}
// RegisterFacade updates the global facade registry with a new version of a new type.
func RegisterFacade(name string, version int, factory facade.Factory, facadeType reflect.Type) {
RegisterFacadeForFeature(name, version, factory, facadeType, "")
}
// RegisterFacadeForFeature updates the global facade registry with a new
// version of a new type. If the feature is non-empty, this facade is only
// available when the specified feature flag is set.
func RegisterFacadeForFeature(name string, version int, factory facade.Factory, facadeType reflect.Type, feature string) {
err := Facades.Register(name, version, factory, facadeType, feature)
if err != nil {
// This is meant to be called during init() so errors should be
// considered fatal.
panic(err)
}
logger.Tracef("Registered facade %q v%d", name, version)
}
type niceFactory func(facade.Context) (interface{}, error)
type nastyFactory func(
st *state.State,
resources facade.Resources,
authorizer facade.Authorizer,
) (
interface{}, error,
)
// validateNewFacade ensures that the facade factory we have has the right
// input and output parameters for being used as a NewFoo function.
func validateNewFacade(funcValue reflect.Value) (bool, error) {
if !funcValue.IsValid() {
return false, fmt.Errorf("cannot wrap nil")
}
if funcValue.Kind() != reflect.Func {
return false, fmt.Errorf("wrong type %q is not a function", funcValue.Kind())
}
funcType := funcValue.Type()
funcName := runtime.FuncForPC(funcValue.Pointer()).Name()
badSigError := errors.Errorf(""+
"function %q does not have the signature "+
"func (facade.Context) (*Type, error), or "+
"func (*state.State, facade.Resources, facade.Authorizer) (*Type, error)", funcName)
if funcType.NumOut() != 2 {
return false, errors.Trace(badSigError)
}
var (
facadeType reflect.Type
nice bool
)
inArgCount := funcType.NumIn()
switch inArgCount {
case 1:
facadeType = reflect.TypeOf((*niceFactory)(nil)).Elem()
nice = true
case 3:
facadeType = reflect.TypeOf((*nastyFactory)(nil)).Elem()
default:
return false, errors.Trace(badSigError)
}
isSame := true
for i := 0; i < inArgCount; i++ {
if funcType.In(i) != facadeType.In(i) {
isSame = false
break
}
}
if funcType.Out(1) != facadeType.Out(1) {
isSame = false
}
if !isSame {
return false, errors.Trace(badSigError)
}
return nice, nil
}
// wrapNewFacade turns a given NewFoo(st, resources, authorizer) (*Instance, error)
// function and wraps it into a proper facade.Factory function.
func wrapNewFacade(newFunc interface{}) (facade.Factory, reflect.Type, error) {
funcValue := reflect.ValueOf(newFunc)
nice, err := validateNewFacade(funcValue)
if err != nil {
return nil, reflect.TypeOf(nil), err
}
var wrapped facade.Factory
if nice {
wrapped = func(context facade.Context) (facade.Facade, error) {
if context.ID() != "" {
return nil, ErrBadId
}
in := []reflect.Value{reflect.ValueOf(context)}
out := funcValue.Call(in)
if out[1].Interface() != nil {
err := out[1].Interface().(error)
return nil, err
}
return out[0].Interface(), nil
}
} else {
// So we know newFunc is a func with the right args in and out, so
// wrap it into a helper function that matches the facade.Factory.
wrapped = func(context facade.Context) (facade.Facade, error) {
if context.ID() != "" {
return nil, ErrBadId
}
st := context.State()
auth := context.Auth()
resources := context.Resources()
// st, resources, or auth is nil, then reflect.Call dies
// because reflect.ValueOf(anynil) is the Zero Value.
// So we use &obj.Elem() which gives us a concrete Value object
// that can refer to nil.
in := []reflect.Value{
reflect.ValueOf(&st).Elem(),
reflect.ValueOf(&resources).Elem(),
reflect.ValueOf(&auth).Elem(),
}
out := funcValue.Call(in)
if out[1].Interface() != nil {
err := out[1].Interface().(error)
return nil, err
}
return out[0].Interface(), nil
}
}
return wrapped, funcValue.Type().Out(0), nil
}
// NewHookContextFacadeFn specifies the function signature that can be
// used to register a hook context facade.
type NewHookContextFacadeFn func(*state.State, *state.Unit) (interface{}, error)
// RegisterHookContextFacade registers facades for use within a hook
// context. This function handles the translation from a
// hook-context-facade to a standard facade so the caller's factory
// method can elide unnecessary arguments. This function also handles
// any necessary authorization for the client.
//
// XXX(fwereade): this is fundamentally broken, because it (1)
// arbitrarily creates a new facade for a tiny fragment of a specific
// client worker's reponsibilities and (2) actively conceals necessary
// auth information from the facade. Don't call it; actively work to
// delete code that uses it, and rewrite it properly.
func RegisterHookContextFacade(name string, version int, newHookContextFacade NewHookContextFacadeFn, facadeType reflect.Type) {
newFacade := func(context facade.Context) (facade.Facade, error) {
authorizer := context.Auth()
st := context.State()
if !authorizer.AuthUnitAgent() {
return nil, ErrPerm
}
// Verify that the unit's ID matches a unit that we know
// about.
tag := authorizer.GetAuthTag()
if _, ok := tag.(names.UnitTag); !ok {
return nil, errors.Errorf("expected names.UnitTag, got %T", tag)
}
unit, err := st.Unit(tag.Id())
if err != nil {
return nil, errors.Trace(err)
}
return newHookContextFacade(st, unit)
}
RegisterFacade(name, version, newFacade, facadeType)
}
// RegisterStandardFacade registers a factory function for a normal New* style
// function. This requires that the function has the form:
// NewFoo(*state.State, facade.Resources, facade.Authorizer) (*Type, error)
// With that syntax, we will create a helper function that wraps calling NewFoo
// with the right parameters, and returns the *Type correctly.
func RegisterStandardFacade(name string, version int, newFunc interface{}) {
RegisterStandardFacadeForFeature(name, version, newFunc, "")
}
// RegisterStandardFacadeForFeature registers a factory function for a normal
// New* style function. This requires that the function has the form:
// NewFoo(*state.State, facade.Resources, facade.Authorizer) (*Type, error)
// With that syntax, we will create a helper function that wraps calling
// NewFoo with the right parameters, and returns the *Type correctly. If the
// feature is non-empty, this facade is only available when the specified
// feature flag is set.
func RegisterStandardFacadeForFeature(name string, version int, newFunc interface{}, feature string) {
wrapped, facadeType, err := wrapNewFacade(newFunc)
if err != nil {
panic(err)
}
RegisterFacadeForFeature(name, version, wrapped, facadeType, feature)
}