mirror of
https://github.com/EchoVault/SugarDB.git
synced 2025-10-05 07:56:52 +08:00
Implemented LoadModules method to load external modules at runtime.
Implemented UnloadModules method to remove modules at runtime. Implemented ListModules method to list the current loaded modules. Implemented "MODULE LOAD", "MODULE UNLOAD", and "MODULE LIST" commands.
This commit is contained in:
9
Makefile
9
Makefile
@@ -1,8 +1,13 @@
|
|||||||
|
build-modules:
|
||||||
|
CGO_ENABLED=$(CGO_ENABLED) CC=$(CC) GOOS=$(GOOS) GOARCH=$(GOARCH) go build -buildmode=plugin -o $(DEST)/module_set/module_set.so ./volumes/modules/module_set/module_set.go && \
|
||||||
|
CGO_ENABLED=$(CGO_ENABLED) CC=$(CC) GOOS=$(GOOS) GOARCH=$(GOARCH) go build -buildmode=plugin -o $(DEST)/module_get/module_get.so ./volumes/modules/module_get/module_get.go
|
||||||
|
|
||||||
build-server:
|
build-server:
|
||||||
CC=$(CC) GOOS=$(GOOS) GOARCH=$(GOARCH) go build -o $(DEST)/server ./cmd/main.go
|
CGO_ENABLED=$(CGO_ENABLED) CC=$(CC) GOOS=$(GOOS) GOARCH=$(GOARCH) go build -o $(DEST)/server ./cmd/main.go
|
||||||
|
|
||||||
build:
|
build:
|
||||||
env CC=x86_64-linux-musl-gcc GOOS=linux GOARCH=amd64 DEST=bin/linux/x86_64 make build-server
|
env CGO_ENABLED=1 CC=x86_64-linux-musl-gcc GOOS=linux GOARCH=amd64 DEST=volumes/modules make build-modules && \
|
||||||
|
env CGO_ENABLED=1 CC=x86_64-linux-musl-gcc GOOS=linux GOARCH=amd64 DEST=bin/linux/x86_64 make build-server
|
||||||
|
|
||||||
run:
|
run:
|
||||||
make build && docker-compose up --build
|
make build && docker-compose up --build
|
||||||
|
@@ -40,15 +40,15 @@ services:
|
|||||||
# List of client certificate authorities
|
# List of client certificate authorities
|
||||||
- CLIENT_CA_1=/etc/ssl/certs/echovault/client/rootCA.crt
|
- CLIENT_CA_1=/etc/ssl/certs/echovault/client/rootCA.crt
|
||||||
# List of shared object plugins to load on startup
|
# List of shared object plugins to load on startup
|
||||||
- MODULE_1=/lib/echovault/modules/module_1.so
|
- MODULE_1=/lib/echovault/modules/module_set/module_set.so
|
||||||
- MODULE_2=/lib/echovault/modules/module_2.so
|
- MODULE_2=/lib/echovault/modules/module_get/module_get.so
|
||||||
ports:
|
ports:
|
||||||
- "7480:7480"
|
- "7480:7480"
|
||||||
- "7946:7946"
|
- "7946:7946"
|
||||||
- "7999:8000"
|
- "7999:8000"
|
||||||
volumes:
|
volumes:
|
||||||
- ./volumes/config:/etc/echovault/config
|
- ./volumes/config:/etc/echovault/config
|
||||||
- ./volumes/plugins:/lib/echovault/plugins
|
- ./volumes/modules:/lib/echovault/modules
|
||||||
- ./volumes/nodes/standalone_node:/var/lib/echovault
|
- ./volumes/nodes/standalone_node:/var/lib/echovault
|
||||||
networks:
|
networks:
|
||||||
- testnet
|
- testnet
|
||||||
@@ -87,8 +87,8 @@ services:
|
|||||||
# List of client certificate authorities
|
# List of client certificate authorities
|
||||||
- CLIENT_CA_1=/etc/ssl/certs/echovault/client/rootCA.crt
|
- CLIENT_CA_1=/etc/ssl/certs/echovault/client/rootCA.crt
|
||||||
# List of shared object plugins to load on startup
|
# List of shared object plugins to load on startup
|
||||||
- MODULE_1=/lib/echovault/modules/module_1.so
|
- MODULE_1=/lib/echovault/modules/module_set/module_set.so
|
||||||
- MODULE_2=/lib/echovault/modules/module_2.so
|
- MODULE_2=/lib/echovault/modules/module_get/module_get.so
|
||||||
ports:
|
ports:
|
||||||
- "7481:7480"
|
- "7481:7480"
|
||||||
- "7945:7946"
|
- "7945:7946"
|
||||||
@@ -134,8 +134,8 @@ services:
|
|||||||
# List of client certificate authorities
|
# List of client certificate authorities
|
||||||
- CLIENT_CA_1=/etc/ssl/certs/echovault/client/rootCA.crt
|
- CLIENT_CA_1=/etc/ssl/certs/echovault/client/rootCA.crt
|
||||||
# List of shared object plugins to load on startup
|
# List of shared object plugins to load on startup
|
||||||
- MODULE_1=/lib/echovault/modules/module_1.so
|
- MODULE_1=/lib/echovault/modules/module_set/module_set.so
|
||||||
- MODULE_2=/lib/echovault/modules/module_2.so
|
- MODULE_2=/lib/echovault/modules/module_get/module_get.so
|
||||||
ports:
|
ports:
|
||||||
- "7482:7480"
|
- "7482:7480"
|
||||||
- "7947:7946"
|
- "7947:7946"
|
||||||
@@ -181,8 +181,8 @@ services:
|
|||||||
# List of client certificate authorities
|
# List of client certificate authorities
|
||||||
- CLIENT_CA_1=/etc/ssl/certs/echovault/client/rootCA.crt
|
- CLIENT_CA_1=/etc/ssl/certs/echovault/client/rootCA.crt
|
||||||
# List of shared object plugins to load on startup
|
# List of shared object plugins to load on startup
|
||||||
- MODULE_1=/lib/echovault/modules/module_1.so
|
- MODULE_1=/lib/echovault/modules/module_set/module_set.so
|
||||||
- MODULE_2=/lib/echovault/modules/module_2.so
|
- MODULE_2=/lib/echovault/modules/module_get/module_get.so
|
||||||
ports:
|
ports:
|
||||||
- "7483:7480"
|
- "7483:7480"
|
||||||
- "7948:7946"
|
- "7948:7946"
|
||||||
@@ -228,8 +228,8 @@ services:
|
|||||||
# List of client certificate authorities
|
# List of client certificate authorities
|
||||||
- CLIENT_CA_1=/etc/ssl/certs/echovault/client/rootCA.crt
|
- CLIENT_CA_1=/etc/ssl/certs/echovault/client/rootCA.crt
|
||||||
# List of shared object plugins to load on startup
|
# List of shared object plugins to load on startup
|
||||||
- MODULE_1=/lib/echovault/modules/module_1.so
|
- MODULE_1=/lib/echovault/modules/module_set/module_set.so
|
||||||
- MODULE_2=/lib/echovault/modules/module_2.so
|
- MODULE_2=/lib/echovault/modules/module_get/module_get.so
|
||||||
ports:
|
ports:
|
||||||
- "7484:7480"
|
- "7484:7480"
|
||||||
- "7949:7946"
|
- "7949:7946"
|
||||||
@@ -275,8 +275,8 @@ services:
|
|||||||
# List of client certificate authorities
|
# List of client certificate authorities
|
||||||
- CLIENT_CA_1=/etc/ssl/certs/echovault/client/rootCA.crt
|
- CLIENT_CA_1=/etc/ssl/certs/echovault/client/rootCA.crt
|
||||||
# List of shared object plugins to load on startup
|
# List of shared object plugins to load on startup
|
||||||
- MODULE_1=/lib/echovault/modules/module_1.so
|
- MODULE_1=/lib/echovault/modules/module_set/module_set.so
|
||||||
- MODULE_2=/lib/echovault/modules/module_2.so
|
- MODULE_2=/lib/echovault/modules/module_get/module_get.so
|
||||||
ports:
|
ports:
|
||||||
- "7485:7480"
|
- "7485:7480"
|
||||||
- "7950:7946"
|
- "7950:7946"
|
||||||
|
@@ -231,6 +231,8 @@ func (server *EchoVault) RewriteAOF() (string, error) {
|
|||||||
//
|
//
|
||||||
// "command <command> already exists" - If a command with the same command name as the passed command already exists.
|
// "command <command> already exists" - If a command with the same command name as the passed command already exists.
|
||||||
func (server *EchoVault) AddCommand(command CommandOptions) error {
|
func (server *EchoVault) AddCommand(command CommandOptions) error {
|
||||||
|
server.commandsRWMut.Lock()
|
||||||
|
defer server.commandsRWMut.Unlock()
|
||||||
// Check if command already exists
|
// Check if command already exists
|
||||||
for _, c := range server.commands {
|
for _, c := range server.commands {
|
||||||
if strings.EqualFold(c.Command, command.Command) {
|
if strings.EqualFold(c.Command, command.Command) {
|
||||||
@@ -398,6 +400,9 @@ func (server *EchoVault) ExecuteCommand(command ...string) ([]byte, error) {
|
|||||||
//
|
//
|
||||||
// `command` - ...string.
|
// `command` - ...string.
|
||||||
func (server *EchoVault) RemoveCommand(command ...string) {
|
func (server *EchoVault) RemoveCommand(command ...string) {
|
||||||
|
server.commandsRWMut.Lock()
|
||||||
|
defer server.commandsRWMut.Unlock()
|
||||||
|
|
||||||
switch len(command) {
|
switch len(command) {
|
||||||
case 1:
|
case 1:
|
||||||
// Remove command
|
// Remove command
|
||||||
|
@@ -82,8 +82,9 @@ type EchoVault struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Holds the list of all commands supported by the echovault.
|
// Holds the list of all commands supported by the echovault.
|
||||||
commands []internal.Command
|
commandsRWMut sync.RWMutex
|
||||||
getCommands func() []internal.Command
|
commands []internal.Command
|
||||||
|
getCommands func() []internal.Command
|
||||||
|
|
||||||
raft *raft.Raft // The raft replication layer for the echovault.
|
raft *raft.Raft // The raft replication layer for the echovault.
|
||||||
memberList *memberlist.MemberList // The memberlist layer for the echovault.
|
memberList *memberlist.MemberList // The memberlist layer for the echovault.
|
||||||
@@ -133,6 +134,7 @@ func NewEchoVault(options ...func(echovault *EchoVault)) (*EchoVault, error) {
|
|||||||
store: make(map[string]internal.KeyData),
|
store: make(map[string]internal.KeyData),
|
||||||
keyLocks: make(map[string]*sync.RWMutex),
|
keyLocks: make(map[string]*sync.RWMutex),
|
||||||
keyCreationLock: &sync.Mutex{},
|
keyCreationLock: &sync.Mutex{},
|
||||||
|
commandsRWMut: sync.RWMutex{},
|
||||||
commands: func() []internal.Command {
|
commands: func() []internal.Command {
|
||||||
var commands []internal.Command
|
var commands []internal.Command
|
||||||
commands = append(commands, acl.Commands()...)
|
commands = append(commands, acl.Commands()...)
|
||||||
@@ -161,8 +163,10 @@ func NewEchoVault(options ...func(echovault *EchoVault)) (*EchoVault, error) {
|
|||||||
// Load .so modules from config
|
// Load .so modules from config
|
||||||
for _, path := range echovault.config.Modules {
|
for _, path := range echovault.config.Modules {
|
||||||
if err := echovault.LoadModule(path); err != nil {
|
if err := echovault.LoadModule(path); err != nil {
|
||||||
log.Println(err)
|
log.Printf("%s %v\n", path, err)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
log.Printf("loaded plugin %s\n", path)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function for server commands retrieval
|
// Function for server commands retrieval
|
||||||
|
@@ -25,6 +25,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (server *EchoVault) getCommand(cmd string) (internal.Command, error) {
|
func (server *EchoVault) getCommand(cmd string) (internal.Command, error) {
|
||||||
|
server.commandsRWMut.RLock()
|
||||||
|
defer server.commandsRWMut.RUnlock()
|
||||||
for _, command := range server.commands {
|
for _, command := range server.commands {
|
||||||
if strings.EqualFold(command.Command, cmd) {
|
if strings.EqualFold(command.Command, cmd) {
|
||||||
return command, nil
|
return command, nil
|
||||||
@@ -52,6 +54,9 @@ func (server *EchoVault) getHandlerFuncParams(ctx context.Context, cmd []string,
|
|||||||
TakeSnapshot: server.takeSnapshot,
|
TakeSnapshot: server.takeSnapshot,
|
||||||
GetLatestSnapshotTime: server.getLatestSnapshotTime,
|
GetLatestSnapshotTime: server.getLatestSnapshotTime,
|
||||||
RewriteAOF: server.rewriteAOF,
|
RewriteAOF: server.rewriteAOF,
|
||||||
|
LoadModule: server.LoadModule,
|
||||||
|
UnloadModule: server.UnloadModule,
|
||||||
|
ListModules: server.ListModules,
|
||||||
GetClock: server.getClock,
|
GetClock: server.getClock,
|
||||||
GetPubSub: server.getPubSub,
|
GetPubSub: server.getPubSub,
|
||||||
GetACL: server.getACL,
|
GetACL: server.getACL,
|
||||||
|
@@ -3,23 +3,25 @@ package echovault
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"github.com/echovault/echovault/internal"
|
"github.com/echovault/echovault/internal"
|
||||||
"plugin"
|
"plugin"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TODO: Add godoc comment
|
||||||
func (server *EchoVault) LoadModule(path string, args ...string) error {
|
func (server *EchoVault) LoadModule(path string, args ...string) error {
|
||||||
p, err := plugin.Open(path)
|
p, err := plugin.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("plugin open: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
commandSymbol, err := p.Lookup("Command")
|
commandSymbol, err := p.Lookup("Command")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
command, ok := commandSymbol.(string)
|
command, ok := commandSymbol.(*string)
|
||||||
if !ok {
|
if !ok {
|
||||||
return errors.New("command symbol is not a string")
|
return errors.New("command symbol is not a string")
|
||||||
}
|
}
|
||||||
@@ -28,7 +30,7 @@ func (server *EchoVault) LoadModule(path string, args ...string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
categories, ok := categoriesSymbol.([]string)
|
categories, ok := categoriesSymbol.(*[]string)
|
||||||
if !ok {
|
if !ok {
|
||||||
return errors.New("categories symbol not a string slice")
|
return errors.New("categories symbol not a string slice")
|
||||||
}
|
}
|
||||||
@@ -37,7 +39,7 @@ func (server *EchoVault) LoadModule(path string, args ...string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
description, ok := descriptionSymbol.(string)
|
description, ok := descriptionSymbol.(*string)
|
||||||
if !ok {
|
if !ok {
|
||||||
return errors.New("description symbol is no a string")
|
return errors.New("description symbol is no a string")
|
||||||
}
|
}
|
||||||
@@ -46,14 +48,14 @@ func (server *EchoVault) LoadModule(path string, args ...string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
sync, ok := syncSymbol.(bool)
|
sync, ok := syncSymbol.(*bool)
|
||||||
if !ok {
|
if !ok {
|
||||||
return errors.New("sync symbol is not a bool")
|
return errors.New("sync symbol is not a bool")
|
||||||
}
|
}
|
||||||
|
|
||||||
keyExtractionFuncSymbol, err := p.Lookup("KeyExtractionFunc")
|
keyExtractionFuncSymbol, err := p.Lookup("KeyExtractionFunc")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("key extraction func symbol: %v", err)
|
||||||
}
|
}
|
||||||
keyExtractionFunc, ok := keyExtractionFuncSymbol.(func(cmd []string, args ...string) ([]string, []string, error))
|
keyExtractionFunc, ok := keyExtractionFuncSymbol.(func(cmd []string, args ...string) ([]string, []string, error))
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -62,7 +64,7 @@ func (server *EchoVault) LoadModule(path string, args ...string) error {
|
|||||||
|
|
||||||
handlerFuncSymbol, err := p.Lookup("HandlerFunc")
|
handlerFuncSymbol, err := p.Lookup("HandlerFunc")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("handler func symbol: %v", err)
|
||||||
}
|
}
|
||||||
handlerFunc, ok := handlerFuncSymbol.(func(
|
handlerFunc, ok := handlerFuncSymbol.(func(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
@@ -81,19 +83,22 @@ func (server *EchoVault) LoadModule(path string, args ...string) error {
|
|||||||
return errors.New("handler function has unexpected signature")
|
return errors.New("handler function has unexpected signature")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
server.commandsRWMut.Lock()
|
||||||
|
defer server.commandsRWMut.Unlock()
|
||||||
|
|
||||||
server.commands = append(server.commands, internal.Command{
|
server.commands = append(server.commands, internal.Command{
|
||||||
Command: command,
|
Command: *command,
|
||||||
Module: path,
|
Module: path,
|
||||||
Categories: func() []string {
|
Categories: func() []string {
|
||||||
// Convert all the categories to lower case for uniformity
|
// Convert all the categories to lower case for uniformity
|
||||||
cats := make([]string, len(categories))
|
cats := make([]string, len(*categories))
|
||||||
for i, cat := range categories {
|
for i, cat := range *categories {
|
||||||
cats[i] = strings.ToLower(cat)
|
cats[i] = strings.ToLower(cat)
|
||||||
}
|
}
|
||||||
return cats
|
return cats
|
||||||
}(),
|
}(),
|
||||||
Description: description,
|
Description: *description,
|
||||||
Sync: sync,
|
Sync: *sync,
|
||||||
SubCommands: make([]internal.SubCommand, 0),
|
SubCommands: make([]internal.SubCommand, 0),
|
||||||
KeyExtractionFunc: func(cmd []string) (internal.KeyExtractionFuncResult, error) {
|
KeyExtractionFunc: func(cmd []string) (internal.KeyExtractionFuncResult, error) {
|
||||||
readKeys, writeKeys, err := keyExtractionFunc(cmd, args...)
|
readKeys, writeKeys, err := keyExtractionFunc(cmd, args...)
|
||||||
@@ -125,8 +130,26 @@ func (server *EchoVault) LoadModule(path string, args ...string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Add godoc comment
|
||||||
func (server *EchoVault) UnloadModule(module string) {
|
func (server *EchoVault) UnloadModule(module string) {
|
||||||
|
server.commandsRWMut.Lock()
|
||||||
|
defer server.commandsRWMut.Unlock()
|
||||||
server.commands = slices.DeleteFunc(server.commands, func(command internal.Command) bool {
|
server.commands = slices.DeleteFunc(server.commands, func(command internal.Command) bool {
|
||||||
return strings.EqualFold(command.Module, module)
|
return strings.EqualFold(command.Module, module)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Add godoc comment
|
||||||
|
func (server *EchoVault) ListModules() []string {
|
||||||
|
server.commandsRWMut.RLock()
|
||||||
|
defer server.commandsRWMut.RUnlock()
|
||||||
|
var modules []string
|
||||||
|
for _, command := range server.commands {
|
||||||
|
if !slices.ContainsFunc(modules, func(module string) bool {
|
||||||
|
return strings.EqualFold(module, command.Module)
|
||||||
|
}) {
|
||||||
|
modules = append(modules, strings.ToLower(command.Module))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return modules
|
||||||
|
}
|
||||||
|
@@ -199,9 +199,7 @@ func Commands() []internal.Command {
|
|||||||
Sync: false,
|
Sync: false,
|
||||||
KeyExtractionFunc: func(cmd []string) (internal.KeyExtractionFuncResult, error) {
|
KeyExtractionFunc: func(cmd []string) (internal.KeyExtractionFuncResult, error) {
|
||||||
return internal.KeyExtractionFuncResult{
|
return internal.KeyExtractionFuncResult{
|
||||||
Channels: make([]string, 0),
|
Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: make([]string, 0),
|
||||||
ReadKeys: make([]string, 0),
|
|
||||||
WriteKeys: make([]string, 0),
|
|
||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
HandlerFunc: handleGetAllCommands,
|
HandlerFunc: handleGetAllCommands,
|
||||||
@@ -214,9 +212,7 @@ func Commands() []internal.Command {
|
|||||||
Sync: false,
|
Sync: false,
|
||||||
KeyExtractionFunc: func(cmd []string) (internal.KeyExtractionFuncResult, error) {
|
KeyExtractionFunc: func(cmd []string) (internal.KeyExtractionFuncResult, error) {
|
||||||
return internal.KeyExtractionFuncResult{
|
return internal.KeyExtractionFuncResult{
|
||||||
Channels: make([]string, 0),
|
Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: make([]string, 0),
|
||||||
ReadKeys: make([]string, 0),
|
|
||||||
WriteKeys: make([]string, 0),
|
|
||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
SubCommands: []internal.SubCommand{
|
SubCommands: []internal.SubCommand{
|
||||||
@@ -228,9 +224,7 @@ func Commands() []internal.Command {
|
|||||||
Sync: false,
|
Sync: false,
|
||||||
KeyExtractionFunc: func(cmd []string) (internal.KeyExtractionFuncResult, error) {
|
KeyExtractionFunc: func(cmd []string) (internal.KeyExtractionFuncResult, error) {
|
||||||
return internal.KeyExtractionFuncResult{
|
return internal.KeyExtractionFuncResult{
|
||||||
Channels: make([]string, 0),
|
Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: make([]string, 0),
|
||||||
ReadKeys: make([]string, 0),
|
|
||||||
WriteKeys: make([]string, 0),
|
|
||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
HandlerFunc: handleCommandDocs,
|
HandlerFunc: handleCommandDocs,
|
||||||
@@ -243,9 +237,7 @@ func Commands() []internal.Command {
|
|||||||
Sync: false,
|
Sync: false,
|
||||||
KeyExtractionFunc: func(cmd []string) (internal.KeyExtractionFuncResult, error) {
|
KeyExtractionFunc: func(cmd []string) (internal.KeyExtractionFuncResult, error) {
|
||||||
return internal.KeyExtractionFuncResult{
|
return internal.KeyExtractionFuncResult{
|
||||||
Channels: make([]string, 0),
|
Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: make([]string, 0),
|
||||||
ReadKeys: make([]string, 0),
|
|
||||||
WriteKeys: make([]string, 0),
|
|
||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
HandlerFunc: handleCommandCount,
|
HandlerFunc: handleCommandCount,
|
||||||
@@ -259,9 +251,7 @@ Allows for filtering by ACL category or glob pattern.`,
|
|||||||
Sync: false,
|
Sync: false,
|
||||||
KeyExtractionFunc: func(cmd []string) (internal.KeyExtractionFuncResult, error) {
|
KeyExtractionFunc: func(cmd []string) (internal.KeyExtractionFuncResult, error) {
|
||||||
return internal.KeyExtractionFuncResult{
|
return internal.KeyExtractionFuncResult{
|
||||||
Channels: make([]string, 0),
|
Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: make([]string, 0),
|
||||||
ReadKeys: make([]string, 0),
|
|
||||||
WriteKeys: make([]string, 0),
|
|
||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
HandlerFunc: handleCommandList,
|
HandlerFunc: handleCommandList,
|
||||||
@@ -276,9 +266,7 @@ Allows for filtering by ACL category or glob pattern.`,
|
|||||||
Sync: true,
|
Sync: true,
|
||||||
KeyExtractionFunc: func(cmd []string) (internal.KeyExtractionFuncResult, error) {
|
KeyExtractionFunc: func(cmd []string) (internal.KeyExtractionFuncResult, error) {
|
||||||
return internal.KeyExtractionFuncResult{
|
return internal.KeyExtractionFuncResult{
|
||||||
Channels: make([]string, 0),
|
Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: make([]string, 0),
|
||||||
ReadKeys: make([]string, 0),
|
|
||||||
WriteKeys: make([]string, 0),
|
|
||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
HandlerFunc: func(params internal.HandlerFuncParams) ([]byte, error) {
|
HandlerFunc: func(params internal.HandlerFuncParams) ([]byte, error) {
|
||||||
@@ -296,9 +284,7 @@ Allows for filtering by ACL category or glob pattern.`,
|
|||||||
Sync: false,
|
Sync: false,
|
||||||
KeyExtractionFunc: func(cmd []string) (internal.KeyExtractionFuncResult, error) {
|
KeyExtractionFunc: func(cmd []string) (internal.KeyExtractionFuncResult, error) {
|
||||||
return internal.KeyExtractionFuncResult{
|
return internal.KeyExtractionFuncResult{
|
||||||
Channels: make([]string, 0),
|
Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: make([]string, 0),
|
||||||
ReadKeys: make([]string, 0),
|
|
||||||
WriteKeys: make([]string, 0),
|
|
||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
HandlerFunc: func(params internal.HandlerFuncParams) ([]byte, error) {
|
HandlerFunc: func(params internal.HandlerFuncParams) ([]byte, error) {
|
||||||
@@ -317,9 +303,7 @@ Allows for filtering by ACL category or glob pattern.`,
|
|||||||
Sync: false,
|
Sync: false,
|
||||||
KeyExtractionFunc: func(cmd []string) (internal.KeyExtractionFuncResult, error) {
|
KeyExtractionFunc: func(cmd []string) (internal.KeyExtractionFuncResult, error) {
|
||||||
return internal.KeyExtractionFuncResult{
|
return internal.KeyExtractionFuncResult{
|
||||||
Channels: make([]string, 0),
|
Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: make([]string, 0),
|
||||||
ReadKeys: make([]string, 0),
|
|
||||||
WriteKeys: make([]string, 0),
|
|
||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
HandlerFunc: func(params internal.HandlerFuncParams) ([]byte, error) {
|
HandlerFunc: func(params internal.HandlerFuncParams) ([]byte, error) {
|
||||||
@@ -329,5 +313,84 @@ Allows for filtering by ACL category or glob pattern.`,
|
|||||||
return []byte(constants.OkResponse), nil
|
return []byte(constants.OkResponse), nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Command: "module",
|
||||||
|
Module: constants.AdminModule,
|
||||||
|
Categories: []string{},
|
||||||
|
Description: "Module commands",
|
||||||
|
KeyExtractionFunc: func(cmd []string) (internal.KeyExtractionFuncResult, error) {
|
||||||
|
return internal.KeyExtractionFuncResult{
|
||||||
|
Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: make([]string, 0),
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
SubCommands: []internal.SubCommand{
|
||||||
|
{
|
||||||
|
Command: "load",
|
||||||
|
Module: constants.AdminModule,
|
||||||
|
Categories: []string{constants.AdminCategory, constants.SlowCategory, constants.DangerousCategory},
|
||||||
|
Description: `(MODULE LOAD path [arg [arg ...]]) Load a module from a dynamic library at runtime.
|
||||||
|
The path should be the full path to the module, including the .so filename. Any args will be be passed unmodified to the
|
||||||
|
module's key extraction and handler functions.`,
|
||||||
|
Sync: true,
|
||||||
|
KeyExtractionFunc: func(cmd []string) (internal.KeyExtractionFuncResult, error) {
|
||||||
|
return internal.KeyExtractionFuncResult{
|
||||||
|
Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: make([]string, 0),
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
HandlerFunc: func(params internal.HandlerFuncParams) ([]byte, error) {
|
||||||
|
if len(params.Command) < 3 {
|
||||||
|
return nil, errors.New(constants.WrongArgsResponse)
|
||||||
|
}
|
||||||
|
var args []string
|
||||||
|
if len(params.Command) > 3 {
|
||||||
|
args = params.Command[3:]
|
||||||
|
}
|
||||||
|
if err := params.LoadModule(params.Command[2], args...); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return []byte(constants.OkResponse), nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Command: "unload",
|
||||||
|
Module: constants.AdminModule,
|
||||||
|
Categories: []string{constants.AdminCategory, constants.SlowCategory, constants.DangerousCategory},
|
||||||
|
Description: `(MODULE UNLOAD name) Unloads a module based on the its name as displayed by the MODULE LIST command.`,
|
||||||
|
Sync: true,
|
||||||
|
KeyExtractionFunc: func(cmd []string) (internal.KeyExtractionFuncResult, error) {
|
||||||
|
return internal.KeyExtractionFuncResult{
|
||||||
|
Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: make([]string, 0),
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
HandlerFunc: func(params internal.HandlerFuncParams) ([]byte, error) {
|
||||||
|
if len(params.Command) != 3 {
|
||||||
|
return nil, errors.New(constants.WrongArgsResponse)
|
||||||
|
}
|
||||||
|
params.UnloadModule(params.Command[2])
|
||||||
|
return []byte(constants.OkResponse), nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Command: "list",
|
||||||
|
Module: constants.AdminModule,
|
||||||
|
Categories: []string{constants.AdminModule, constants.SlowCategory, constants.DangerousCategory},
|
||||||
|
Description: `(MODULE LIST) List all the modules that are currently loaded in the server.`,
|
||||||
|
Sync: false,
|
||||||
|
KeyExtractionFunc: func(cmd []string) (internal.KeyExtractionFuncResult, error) {
|
||||||
|
return internal.KeyExtractionFuncResult{
|
||||||
|
Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: make([]string, 0),
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
HandlerFunc: func(params internal.HandlerFuncParams) ([]byte, error) {
|
||||||
|
modules := params.ListModules()
|
||||||
|
res := fmt.Sprintf("*%d\r\n", len(modules))
|
||||||
|
for _, module := range modules {
|
||||||
|
res += fmt.Sprintf("$%d\r\n%s\r\n", len(module), module)
|
||||||
|
}
|
||||||
|
return []byte(res), nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -78,6 +78,9 @@ type HandlerFuncParams struct {
|
|||||||
TakeSnapshot func() error
|
TakeSnapshot func() error
|
||||||
RewriteAOF func() error
|
RewriteAOF func() error
|
||||||
GetLatestSnapshotTime func() int64
|
GetLatestSnapshotTime func() int64
|
||||||
|
LoadModule func(path string, args ...string) error
|
||||||
|
UnloadModule func(module string)
|
||||||
|
ListModules func() []string
|
||||||
}
|
}
|
||||||
|
|
||||||
type HandlerFunc func(params HandlerFuncParams) ([]byte, error)
|
type HandlerFunc func(params HandlerFuncParams) ([]byte, error)
|
||||||
|
@@ -1 +0,0 @@
|
|||||||
package modules
|
|
@@ -1 +0,0 @@
|
|||||||
package modules
|
|
59
volumes/modules/module_get/module_get.go
Normal file
59
volumes/modules/module_get/module_get.go
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Command string = "Module.Get"
|
||||||
|
|
||||||
|
var Categories []string = []string{"read", "fast"}
|
||||||
|
|
||||||
|
var Description string = `(Module.Get key) This module fetches the integer value from the key and returns the value ^ 2.
|
||||||
|
0 is returned if the key does not exist. An error is returned if the value is not an integer.`
|
||||||
|
|
||||||
|
var Sync bool = false
|
||||||
|
|
||||||
|
func KeyExtractionFunc(cmd []string, args ...string) ([]string, []string, error) {
|
||||||
|
if len(cmd) != 2 {
|
||||||
|
return nil, nil, fmt.Errorf("wrong no of args for %s command", Command)
|
||||||
|
}
|
||||||
|
return cmd[1:], []string{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandlerFunc(
|
||||||
|
ctx context.Context,
|
||||||
|
command []string,
|
||||||
|
keyExists func(ctx context.Context, key string) bool,
|
||||||
|
keyLock func(ctx context.Context, key string) (bool, error),
|
||||||
|
keyUnlock func(ctx context.Context, key string),
|
||||||
|
keyRLock func(ctx context.Context, key string) (bool, error),
|
||||||
|
keyRUnlock func(ctx context.Context, key string),
|
||||||
|
createKeyAndLock func(ctx context.Context, key string) (bool, error),
|
||||||
|
getValue func(ctx context.Context, key string) interface{},
|
||||||
|
setValue func(ctx context.Context, key string, value interface{}) error,
|
||||||
|
args ...string) ([]byte, error) {
|
||||||
|
|
||||||
|
readKeys, _, err := KeyExtractionFunc(command, args...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
key := readKeys[0]
|
||||||
|
|
||||||
|
if !keyExists(ctx, key) {
|
||||||
|
return []byte(":0\r\n"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = keyRLock(ctx, key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer keyRUnlock(ctx, key)
|
||||||
|
|
||||||
|
val, ok := getValue(ctx, key).(int64)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("value at key %s is not an integer", key)
|
||||||
|
}
|
||||||
|
|
||||||
|
return []byte(fmt.Sprintf(":%d\r\n", val*val)), nil
|
||||||
|
}
|
BIN
volumes/modules/module_get/module_get.so
Normal file
BIN
volumes/modules/module_get/module_get.so
Normal file
Binary file not shown.
68
volumes/modules/module_set/module_set.go
Normal file
68
volumes/modules/module_set/module_set.go
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Command string = "Module.Set"
|
||||||
|
|
||||||
|
var Categories []string = []string{"write", "fast"}
|
||||||
|
|
||||||
|
var Description string = `(Module.Set key value) This module stores the given value at the specified key.
|
||||||
|
The value must be an integer`
|
||||||
|
|
||||||
|
var Sync bool = true
|
||||||
|
|
||||||
|
func KeyExtractionFunc(cmd []string, args ...string) ([]string, []string, error) {
|
||||||
|
if len(cmd) != 3 {
|
||||||
|
return nil, nil, fmt.Errorf("wrong no of args for %s command", Command)
|
||||||
|
}
|
||||||
|
return []string{}, cmd[1:2], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandlerFunc(
|
||||||
|
ctx context.Context,
|
||||||
|
command []string,
|
||||||
|
keyExists func(ctx context.Context, key string) bool,
|
||||||
|
keyLock func(ctx context.Context, key string) (bool, error),
|
||||||
|
keyUnlock func(ctx context.Context, key string),
|
||||||
|
keyRLock func(ctx context.Context, key string) (bool, error),
|
||||||
|
keyRUnlock func(ctx context.Context, key string),
|
||||||
|
createKeyAndLock func(ctx context.Context, key string) (bool, error),
|
||||||
|
getValue func(ctx context.Context, key string) interface{},
|
||||||
|
setValue func(ctx context.Context, key string, value interface{}) error,
|
||||||
|
args ...string) ([]byte, error) {
|
||||||
|
|
||||||
|
_, writeKeys, err := KeyExtractionFunc(command, args...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
key := writeKeys[0]
|
||||||
|
|
||||||
|
if !keyExists(ctx, key) {
|
||||||
|
_, err := createKeyAndLock(ctx, key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_, err := keyLock(ctx, key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defer keyUnlock(ctx, key)
|
||||||
|
|
||||||
|
value, err := strconv.ParseInt(command[2], 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = setValue(ctx, key, value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return []byte("+OK\r\n"), nil
|
||||||
|
}
|
BIN
volumes/modules/module_set/module_set.so
Normal file
BIN
volumes/modules/module_set/module_set.so
Normal file
Binary file not shown.
Reference in New Issue
Block a user