Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WiP - Go Plug-in/Module Support #575

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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