Skip to content

Commit 0a6b34a

Browse files
committed
Go Plug-in/Module Support
This patch replaces PR #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.
1 parent cb8a4bc commit 0a6b34a

16 files changed

Lines changed: 1046 additions & 1501 deletions

Makefile

Lines changed: 13 additions & 1481 deletions
Large diffs are not rendered by default.

api/mods/mods.go

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
// +build go1.8,linux,mods
2+
3+
package mods
4+
5+
import (
6+
"fmt"
7+
"os"
8+
"path"
9+
"path/filepath"
10+
"plugin"
11+
"strconv"
12+
"sync"
13+
14+
"github.com/akutz/gotil"
15+
16+
"github.com/codedellemc/libstorage/api/registry"
17+
"github.com/codedellemc/libstorage/api/types"
18+
)
19+
20+
var (
21+
loadedMods = map[string]bool{}
22+
loadedModsLock = sync.Mutex{}
23+
)
24+
25+
// LoadModules loads the shared objects present on the file system
26+
// as libStorage plug-ins.
27+
func LoadModules(
28+
ctx types.Context,
29+
pathConfig *types.PathConfig) {
30+
31+
disabled, _ := strconv.ParseBool(
32+
os.Getenv("LIBSTORAGE_PLUGINS_DISABLED"))
33+
if disabled {
34+
ctx.Debug("plugin support disabled")
35+
return
36+
}
37+
38+
loadedModsLock.Lock()
39+
defer loadedModsLock.Unlock()
40+
41+
if !gotil.FileExists(pathConfig.Mod) {
42+
return
43+
}
44+
45+
modFilePathMatches, err := filepath.Glob(path.Join(pathConfig.Mod, "/*.so"))
46+
if err != nil {
47+
// since the only possible error is ErrBadPattern then make sure
48+
// it panics the program since it should never be an invalid pattern
49+
panic(err)
50+
}
51+
52+
for _, modFilePath := range modFilePathMatches {
53+
ctx.WithField(
54+
"path", modFilePath).Debug(
55+
"loading module")
56+
57+
if loaded, ok := loadedMods[modFilePath]; ok && loaded {
58+
ctx.WithField(
59+
"path", modFilePath).Debug(
60+
"already loaded")
61+
continue
62+
}
63+
64+
p, err := plugin.Open(modFilePath)
65+
if err != nil {
66+
ctx.WithError(err).WithField(
67+
"path", modFilePath).Error(
68+
"error opening module")
69+
continue
70+
}
71+
72+
if err := loadPluginTypes(ctx, p); err != nil {
73+
continue
74+
}
75+
76+
loadedMods[lcModFilePath] = true
77+
ctx.WithField(
78+
"path", modFilePath).Info(
79+
"loaded module")
80+
}
81+
}
82+
83+
func loadPluginTypes(ctx types.Context, p plugin.Plugin) error {
84+
// lookup the plug-in's Types symbol; it's the type map used to
85+
// register the plug-in's modules
86+
tmapObj, err := p.Lookup("Types")
87+
if err != nil {
88+
ctx.WithError(err).Error("error looking up type map")
89+
return err
90+
}
91+
92+
// assert that the Types symbol is a *map[string]func() interface{}
93+
tmapPtr, tmapOk := tmapObj.(*map[string]func() interface{})
94+
if !tmapOk {
95+
err := fmt.Errorf("invalid type map: %T", tmapObj)
96+
ctx.Error(err)
97+
return err
98+
}
99+
100+
// assert that the type map pointer is not nil
101+
if tmapPtr == nil {
102+
err := fmt.Errorf("nil type map: type=%[1]T val=%[1]v", tmapPtr)
103+
ctx.Error(err)
104+
return err
105+
}
106+
107+
// dereference the type map pointer
108+
tmap := *tmapPtr
109+
110+
// register the plug-in's modules
111+
for k, v := range tmap {
112+
registry.RegisterModType(k, v)
113+
}
114+
}

api/mods/nomods.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// +build !go1.8 !linux !mods
2+
3+
package mods
4+
5+
import (
6+
"github.com/codedellemc/libstorage/api/types"
7+
)
8+
9+
// LoadModules loads the shared objects present on the file system
10+
// as libStorage plug-ins.
11+
func LoadModules(
12+
ctx types.Context,
13+
pathConfig *types.PathConfig) {
14+
15+
// Do nothing
16+
}

api/registry/registry.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,21 @@
33
package registry
44

55
import (
6+
"fmt"
67
"strings"
78
"sync"
89

910
gofig "github.com/akutz/gofig/types"
1011
"github.com/akutz/goof"
1112

1213
"github.com/codedellemc/libstorage/api/types"
14+
apitypesV1 "github.com/codedellemc/libstorage/api/types/v1"
1315
)
1416

1517
var (
18+
modTypeCtors = map[string]func() interface{}{}
19+
modTypeCtorsRWL = &sync.RWMutex{}
20+
1621
storExecsCtors = map[string]types.NewStorageExecutor{}
1722
storExecsCtorsRWL = &sync.RWMutex{}
1823

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

52+
// RegisterModType registers a type from a plug-in mod.
53+
func RegisterModType(name string, ctor func() interface{}) {
54+
modTypeCtorsRWL.Lock()
55+
defer modTypeCtorsRWL.Unlock()
56+
modTypeCtors[strings.ToLower(name)] = ctor
57+
}
58+
4759
// RegisterRouter registers a Router.
4860
func RegisterRouter(router types.Router) {
4961
routersRWL.Lock()
@@ -80,6 +92,31 @@ func RegisterIntegrationDriver(name string, ctor types.NewIntegrationDriver) {
8092
intDriverCtors[strings.ToLower(name)] = ctor
8193
}
8294

95+
// NewModType returns a new instance of the specified module type.
96+
func NewModType(name string) (apitypesV1.Driver, error) {
97+
98+
var ok bool
99+
var ctor func() interface{}
100+
101+
func() {
102+
modTypeCtorsRWL.RLock()
103+
defer modTypeCtorsRWL.RUnlock()
104+
ctor, ok = modTypeCtors[name]
105+
}()
106+
107+
if !ok {
108+
return nil, goof.WithField("modType", name, "invalid type name")
109+
}
110+
111+
d, ok := ctor().(apitypesV1.Driver)
112+
if !ok {
113+
return nil, goof.WithField(
114+
"modType", fmt.Sprintf("%T", d), "invalid type")
115+
}
116+
117+
return d, nil
118+
}
119+
83120
// NewStorageExecutor returns a new instance of the executor specified by the
84121
// executor name.
85122
func NewStorageExecutor(name string) (types.StorageExecutor, error) {
@@ -177,6 +214,23 @@ func ConfigRegs(ctx types.Context) <-chan gofig.ConfigRegistration {
177214
return c
178215
}
179216

217+
// ModTypes returns a channel on which new instances of all registered
218+
// module types.
219+
func ModTypes() <-chan apitypesV1.Driver {
220+
c := make(chan apitypesV1.Driver)
221+
go func() {
222+
modTypeCtorsRWL.RLock()
223+
defer modTypeCtorsRWL.RUnlock()
224+
for _, ctor := range modTypeCtors {
225+
if d, ok := ctor().(apitypesV1.Driver); ok {
226+
c <- d
227+
}
228+
}
229+
close(c)
230+
}()
231+
return c
232+
}
233+
180234
// StorageExecutors returns a channel on which new instances of all registered
181235
// storage executors can be received.
182236
func StorageExecutors() <-chan types.StorageExecutor {

api/types/types_paths.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ type PathConfig struct {
1515
// Lib is the path to the lib directory.
1616
Lib string
1717

18+
// Mod is the path to the mod directory.
19+
Mod string
20+
1821
// Log is the path to the log directory.
1922
Log string
2023

api/types/v1/v1_auth.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package v1
2+
3+
// AuthToken is a JSON Web Token.
4+
//
5+
// All fields related to times are stored as UTC epochs in seconds.
6+
type AuthToken interface {
7+
8+
// GetSubject is the intended principal of the token.
9+
GetSubject() string
10+
11+
// GetExpires is the time at which the token expires.
12+
GetExpires() int64
13+
14+
// GetNotBefore is the the time at which the token becomes valid.
15+
GetNotBefore() int64
16+
17+
// GetIssuedAt is the time at which the token was issued.
18+
GetIssuedAt() int64
19+
20+
// GetEncoded is the encoded JWT string.
21+
GetEncoded() string
22+
}
23+
24+
// AuthConfig is the auth configuration.
25+
type AuthConfig interface {
26+
27+
// GetDisabled is a flag indicating whether the auth configuration is
28+
// disabled.
29+
GetDisabled() bool
30+
31+
// GetAllow is a list of allowed tokens.
32+
GetAllow() []string
33+
34+
// GetDeny is a list of denied tokens.
35+
GetDeny() []string
36+
37+
// GetKey is the signing key.
38+
GetKey() []byte
39+
40+
// GetAlg is the cryptographic algorithm used to sign and verify the token.
41+
GetAlg() string
42+
}

api/types/v1/v1_config.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package v1
2+
3+
// Config is the interface that enables retrieving configuration information.
4+
// The variations of the Get function, the Set, IsSet, and Scope functions
5+
// all take an interface{} as their first parameter. However, the param must be
6+
// either a string or a fmt.Stringer, otherwise the function will panic.
7+
type Config interface {
8+
9+
// GetString returns the value associated with the key as a string
10+
GetString(k interface{}) string
11+
12+
// GetBool returns the value associated with the key as a bool
13+
GetBool(k interface{}) bool
14+
15+
// GetStringSlice returns the value associated with the key as a string
16+
// slice.
17+
GetStringSlice(k interface{}) []string
18+
19+
// GetInt returns the value associated with the key as an int
20+
GetInt(k interface{}) int
21+
22+
// Get returns the value associated with the key
23+
Get(k interface{}) interface{}
24+
25+
// Set sets an override value
26+
Set(k interface{}, v interface{})
27+
28+
// IsSet returns a flag indicating whether or not a key is set.
29+
IsSet(k interface{}) bool
30+
}

api/types/v1/v1_context.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package v1
2+
3+
import "context"
4+
5+
// Context is an extension of the Go Context.
6+
type Context interface {
7+
context.Context
8+
}
9+
10+
// ContextLoggerFieldAware is used by types that will be logged by the
11+
// Context logger. The key/value pair returned by the type is then emitted
12+
// as part of the Context's log entry.
13+
type ContextLoggerFieldAware interface {
14+
15+
// ContextLoggerField is the fields that is logged as part of a Context's
16+
// log entry.
17+
ContextLoggerField() (string, interface{})
18+
}
19+
20+
// ContextLoggerFieldsAware is used by types that will be logged by the
21+
// Context logger. The fields returned by the type are then emitted as part of
22+
// the Context's log entry.
23+
type ContextLoggerFieldsAware interface {
24+
25+
// ContextLoggerFields are the fields that are logged as part of a
26+
// Context's log entry.
27+
ContextLoggerFields() map[string]interface{}
28+
}

api/types/v1/v1_driver.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package v1
2+
3+
// Driver the interface for types that are drivers.
4+
type Driver interface {
5+
6+
// Do does the operation specified by the opID.
7+
Do(
8+
ctx interface{},
9+
opID uint64,
10+
args ...interface{}) (result interface{}, err error)
11+
12+
// Init initializes the driver instance.
13+
Init(ctx, config interface{}) error
14+
15+
// Name returns the name of the driver.
16+
Name() string
17+
18+
// Supports returns a mask of the operations supported by the driver.
19+
Supports() uint64
20+
}
21+
22+
const (
23+
// DtStorage indicates the storage driver type.
24+
DtStorage uint8 = 1 << iota
25+
26+
// DtIntegration indicates the integration driver type.
27+
DtIntegration
28+
)

api/types/v1/v1_errors.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package v1
2+
3+
import "errors"
4+
5+
// ErrInvalidContext indicates an invalid object was provided as
6+
// the argument to a Context parameter.
7+
var ErrInvalidContext = errors.New("invalid context type")
8+
9+
// ErrInvalidConfig indicates an invalid object was provided as
10+
// the argument to a Config parameter.
11+
var ErrInvalidConfig = errors.New("invalid config type")
12+
13+
// ErrInvalidOp indicates an invalid operation ID.
14+
var ErrInvalidOp = errors.New("invalid op")
15+
16+
// ErrInvalidArgsLen indicates the operation received an incorrect
17+
// number of arguments.
18+
var ErrInvalidArgsLen = errors.New("invalid args len")
19+
20+
// ErrInvalidArgs indicates the operation received one or more
21+
// invalid arguments.
22+
var ErrInvalidArgs = errors.New("invalid args")

0 commit comments

Comments
 (0)