Skip to content

Commit

Permalink
Go Plug-in/Module Support
Browse files Browse the repository at this point in the history
This patch replaces PR thecodeteam#557 and introduces support for type-isolated Go
plug-ins. The 'api/types/v1' package represents an initial version of
the libStorage type system transformed into interfaces for use by
independent, separate module files.
  • Loading branch information
akutz committed Jun 15, 2017
1 parent cb8a4bc commit c2cb459
Show file tree
Hide file tree
Showing 16 changed files with 1,050 additions and 1,501 deletions.
1,494 changes: 13 additions & 1,481 deletions Makefile

Large diffs are not rendered by default.

118 changes: 118 additions & 0 deletions api/mods/mods.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// +build go1.8,linux,mods

package mods

import (
"fmt"
"os"
"path"
"path/filepath"
"plugin"
"strconv"
"sync"

"github.com/akutz/gotil"

"github.com/codedellemc/libstorage/api/registry"
"github.com/codedellemc/libstorage/api/types"
)

var (
loadedMods = map[string]bool{}
loadedModsLock = sync.Mutex{}
)

// LoadModules loads the shared objects present on the file system
// as libStorage plug-ins.
func LoadModules(
ctx types.Context,
pathConfig *types.PathConfig) error {

disabled, _ := strconv.ParseBool(
os.Getenv("LIBSTORAGE_PLUGINS_DISABLED"))
if disabled {
ctx.Debug("plugin support disabled")
return nil
}

loadedModsLock.Lock()
defer loadedModsLock.Unlock()

if !gotil.FileExists(pathConfig.Mod) {
return fmt.Errorf("error: invalid mod dir: %v", pathConfig.Mod)
}

modFilePathMatches, err := filepath.Glob(path.Join(pathConfig.Mod, "/*.so"))
if err != nil {
// since the only possible error is ErrBadPattern then make sure
// it panics the program since it should never be an invalid pattern
panic(err)
}

for _, modFilePath := range modFilePathMatches {
ctx.WithField(
"path", modFilePath).Debug(
"loading module")

if loaded, ok := loadedMods[modFilePath]; ok && loaded {
ctx.WithField(
"path", modFilePath).Debug(
"already loaded")
continue
}

p, err := plugin.Open(modFilePath)
if err != nil {
ctx.WithError(err).WithField(
"path", modFilePath).Error(
"error opening module")
continue
}

if err := loadPluginTypes(ctx, p); err != nil {
continue
}

loadedMods[modFilePath] = true
ctx.WithField(
"path", modFilePath).Info(
"loaded module")
}

return nil
}

func loadPluginTypes(ctx types.Context, p *plugin.Plugin) error {
// lookup the plug-in's Types symbol; it's the type map used to
// register the plug-in's modules
tmapObj, err := p.Lookup("Types")
if err != nil {
ctx.WithError(err).Error("error looking up type map")
return err
}

// assert that the Types symbol is a *map[string]func() interface{}
tmapPtr, tmapOk := tmapObj.(*map[string]func() interface{})
if !tmapOk {
err := fmt.Errorf("invalid type map: %T", tmapObj)
ctx.Error(err)
return err
}

// assert that the type map pointer is not nil
if tmapPtr == nil {
err := fmt.Errorf("nil type map: type=%[1]T val=%[1]v", tmapPtr)
ctx.Error(err)
return err
}

// dereference the type map pointer
tmap := *tmapPtr

// register the plug-in's modules
for k, v := range tmap {
registry.RegisterModType(k, v)
}

return nil
}
16 changes: 16 additions & 0 deletions api/mods/nomods.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// +build !go1.8 !linux !mods

package mods

import (
"github.com/codedellemc/libstorage/api/types"
)

// LoadModules loads the shared objects present on the file system
// as libStorage plug-ins.
func LoadModules(
ctx types.Context,
pathConfig *types.PathConfig) {

// Do nothing
}
54 changes: 54 additions & 0 deletions api/registry/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,21 @@
package registry

import (
"fmt"
"strings"
"sync"

gofig "github.com/akutz/gofig/types"
"github.com/akutz/goof"

"github.com/codedellemc/libstorage/api/types"
apitypesV1 "github.com/codedellemc/libstorage/api/types/v1"
)

var (
modTypeCtors = map[string]func() interface{}{}
modTypeCtorsRWL = &sync.RWMutex{}

storExecsCtors = map[string]types.NewStorageExecutor{}
storExecsCtorsRWL = &sync.RWMutex{}

Expand Down Expand Up @@ -44,6 +49,13 @@ func RegisterConfigReg(name string, f types.NewConfigReg) {
cfgRegs = append(cfgRegs, &cregW{name, f})
}

// RegisterModType registers a type from a plug-in mod.
func RegisterModType(name string, ctor func() interface{}) {
modTypeCtorsRWL.Lock()
defer modTypeCtorsRWL.Unlock()
modTypeCtors[strings.ToLower(name)] = ctor
}

// RegisterRouter registers a Router.
func RegisterRouter(router types.Router) {
routersRWL.Lock()
Expand Down Expand Up @@ -80,6 +92,31 @@ func RegisterIntegrationDriver(name string, ctor types.NewIntegrationDriver) {
intDriverCtors[strings.ToLower(name)] = ctor
}

// NewModType returns a new instance of the specified module type.
func NewModType(name string) (apitypesV1.Driver, error) {

var ok bool
var ctor func() interface{}

func() {
modTypeCtorsRWL.RLock()
defer modTypeCtorsRWL.RUnlock()
ctor, ok = modTypeCtors[name]
}()

if !ok {
return nil, goof.WithField("modType", name, "invalid type name")
}

d, ok := ctor().(apitypesV1.Driver)
if !ok {
return nil, goof.WithField(
"modType", fmt.Sprintf("%T", d), "invalid type")
}

return d, nil
}

// NewStorageExecutor returns a new instance of the executor specified by the
// executor name.
func NewStorageExecutor(name string) (types.StorageExecutor, error) {
Expand Down Expand Up @@ -177,6 +214,23 @@ func ConfigRegs(ctx types.Context) <-chan gofig.ConfigRegistration {
return c
}

// ModTypes returns a channel on which new instances of all registered
// module types.
func ModTypes() <-chan apitypesV1.Driver {
c := make(chan apitypesV1.Driver)
go func() {
modTypeCtorsRWL.RLock()
defer modTypeCtorsRWL.RUnlock()
for _, ctor := range modTypeCtors {
if d, ok := ctor().(apitypesV1.Driver); ok {
c <- d
}
}
close(c)
}()
return c
}

// StorageExecutors returns a channel on which new instances of all registered
// storage executors can be received.
func StorageExecutors() <-chan types.StorageExecutor {
Expand Down
3 changes: 3 additions & 0 deletions api/types/types_paths.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ type PathConfig struct {
// Lib is the path to the lib directory.
Lib string

// Mod is the path to the mod directory.
Mod string

// Log is the path to the log directory.
Log string

Expand Down
42 changes: 42 additions & 0 deletions api/types/v1/v1_auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package v1

// AuthToken is a JSON Web Token.
//
// All fields related to times are stored as UTC epochs in seconds.
type AuthToken interface {

// GetSubject is the intended principal of the token.
GetSubject() string

// GetExpires is the time at which the token expires.
GetExpires() int64

// GetNotBefore is the the time at which the token becomes valid.
GetNotBefore() int64

// GetIssuedAt is the time at which the token was issued.
GetIssuedAt() int64

// GetEncoded is the encoded JWT string.
GetEncoded() string
}

// AuthConfig is the auth configuration.
type AuthConfig interface {

// GetDisabled is a flag indicating whether the auth configuration is
// disabled.
GetDisabled() bool

// GetAllow is a list of allowed tokens.
GetAllow() []string

// GetDeny is a list of denied tokens.
GetDeny() []string

// GetKey is the signing key.
GetKey() []byte

// GetAlg is the cryptographic algorithm used to sign and verify the token.
GetAlg() string
}
30 changes: 30 additions & 0 deletions api/types/v1/v1_config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package v1

// Config is the interface that enables retrieving configuration information.
// The variations of the Get function, the Set, IsSet, and Scope functions
// all take an interface{} as their first parameter. However, the param must be
// either a string or a fmt.Stringer, otherwise the function will panic.
type Config interface {

// GetString returns the value associated with the key as a string
GetString(k interface{}) string

// GetBool returns the value associated with the key as a bool
GetBool(k interface{}) bool

// GetStringSlice returns the value associated with the key as a string
// slice.
GetStringSlice(k interface{}) []string

// GetInt returns the value associated with the key as an int
GetInt(k interface{}) int

// Get returns the value associated with the key
Get(k interface{}) interface{}

// Set sets an override value
Set(k interface{}, v interface{})

// IsSet returns a flag indicating whether or not a key is set.
IsSet(k interface{}) bool
}
28 changes: 28 additions & 0 deletions api/types/v1/v1_context.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package v1

import "context"

// Context is an extension of the Go Context.
type Context interface {
context.Context
}

// ContextLoggerFieldAware is used by types that will be logged by the
// Context logger. The key/value pair returned by the type is then emitted
// as part of the Context's log entry.
type ContextLoggerFieldAware interface {

// ContextLoggerField is the fields that is logged as part of a Context's
// log entry.
ContextLoggerField() (string, interface{})
}

// ContextLoggerFieldsAware is used by types that will be logged by the
// Context logger. The fields returned by the type are then emitted as part of
// the Context's log entry.
type ContextLoggerFieldsAware interface {

// ContextLoggerFields are the fields that are logged as part of a
// Context's log entry.
ContextLoggerFields() map[string]interface{}
}
28 changes: 28 additions & 0 deletions api/types/v1/v1_driver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package v1

// Driver the interface for types that are drivers.
type Driver interface {

// Do does the operation specified by the opID.
Do(
ctx interface{},
opID uint64,
args ...interface{}) (result interface{}, err error)

// Init initializes the driver instance.
Init(ctx, config interface{}) error

// Name returns the name of the driver.
Name() string

// Supports returns a mask of the operations supported by the driver.
Supports() uint64
}

const (
// DtStorage indicates the storage driver type.
DtStorage uint8 = 1 << iota

// DtIntegration indicates the integration driver type.
DtIntegration
)
22 changes: 22 additions & 0 deletions api/types/v1/v1_errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package v1

import "errors"

// ErrInvalidContext indicates an invalid object was provided as
// the argument to a Context parameter.
var ErrInvalidContext = errors.New("invalid context type")

// ErrInvalidConfig indicates an invalid object was provided as
// the argument to a Config parameter.
var ErrInvalidConfig = errors.New("invalid config type")

// ErrInvalidOp indicates an invalid operation ID.
var ErrInvalidOp = errors.New("invalid op")

// ErrInvalidArgsLen indicates the operation received an incorrect
// number of arguments.
var ErrInvalidArgsLen = errors.New("invalid args len")

// ErrInvalidArgs indicates the operation received one or more
// invalid arguments.
var ErrInvalidArgs = errors.New("invalid args")
Loading

0 comments on commit c2cb459

Please sign in to comment.