mirror of
https://github.com/EchoVault/SugarDB.git
synced 2025-10-06 00:16:53 +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:
|
||||
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:
|
||||
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:
|
||||
make build && docker-compose up --build
|
||||
|
@@ -40,15 +40,15 @@ services:
|
||||
# List of client certificate authorities
|
||||
- CLIENT_CA_1=/etc/ssl/certs/echovault/client/rootCA.crt
|
||||
# List of shared object plugins to load on startup
|
||||
- MODULE_1=/lib/echovault/modules/module_1.so
|
||||
- MODULE_2=/lib/echovault/modules/module_2.so
|
||||
- MODULE_1=/lib/echovault/modules/module_set/module_set.so
|
||||
- MODULE_2=/lib/echovault/modules/module_get/module_get.so
|
||||
ports:
|
||||
- "7480:7480"
|
||||
- "7946:7946"
|
||||
- "7999:8000"
|
||||
volumes:
|
||||
- ./volumes/config:/etc/echovault/config
|
||||
- ./volumes/plugins:/lib/echovault/plugins
|
||||
- ./volumes/modules:/lib/echovault/modules
|
||||
- ./volumes/nodes/standalone_node:/var/lib/echovault
|
||||
networks:
|
||||
- testnet
|
||||
@@ -87,8 +87,8 @@ services:
|
||||
# List of client certificate authorities
|
||||
- CLIENT_CA_1=/etc/ssl/certs/echovault/client/rootCA.crt
|
||||
# List of shared object plugins to load on startup
|
||||
- MODULE_1=/lib/echovault/modules/module_1.so
|
||||
- MODULE_2=/lib/echovault/modules/module_2.so
|
||||
- MODULE_1=/lib/echovault/modules/module_set/module_set.so
|
||||
- MODULE_2=/lib/echovault/modules/module_get/module_get.so
|
||||
ports:
|
||||
- "7481:7480"
|
||||
- "7945:7946"
|
||||
@@ -134,8 +134,8 @@ services:
|
||||
# List of client certificate authorities
|
||||
- CLIENT_CA_1=/etc/ssl/certs/echovault/client/rootCA.crt
|
||||
# List of shared object plugins to load on startup
|
||||
- MODULE_1=/lib/echovault/modules/module_1.so
|
||||
- MODULE_2=/lib/echovault/modules/module_2.so
|
||||
- MODULE_1=/lib/echovault/modules/module_set/module_set.so
|
||||
- MODULE_2=/lib/echovault/modules/module_get/module_get.so
|
||||
ports:
|
||||
- "7482:7480"
|
||||
- "7947:7946"
|
||||
@@ -181,8 +181,8 @@ services:
|
||||
# List of client certificate authorities
|
||||
- CLIENT_CA_1=/etc/ssl/certs/echovault/client/rootCA.crt
|
||||
# List of shared object plugins to load on startup
|
||||
- MODULE_1=/lib/echovault/modules/module_1.so
|
||||
- MODULE_2=/lib/echovault/modules/module_2.so
|
||||
- MODULE_1=/lib/echovault/modules/module_set/module_set.so
|
||||
- MODULE_2=/lib/echovault/modules/module_get/module_get.so
|
||||
ports:
|
||||
- "7483:7480"
|
||||
- "7948:7946"
|
||||
@@ -228,8 +228,8 @@ services:
|
||||
# List of client certificate authorities
|
||||
- CLIENT_CA_1=/etc/ssl/certs/echovault/client/rootCA.crt
|
||||
# List of shared object plugins to load on startup
|
||||
- MODULE_1=/lib/echovault/modules/module_1.so
|
||||
- MODULE_2=/lib/echovault/modules/module_2.so
|
||||
- MODULE_1=/lib/echovault/modules/module_set/module_set.so
|
||||
- MODULE_2=/lib/echovault/modules/module_get/module_get.so
|
||||
ports:
|
||||
- "7484:7480"
|
||||
- "7949:7946"
|
||||
@@ -275,8 +275,8 @@ services:
|
||||
# List of client certificate authorities
|
||||
- CLIENT_CA_1=/etc/ssl/certs/echovault/client/rootCA.crt
|
||||
# List of shared object plugins to load on startup
|
||||
- MODULE_1=/lib/echovault/modules/module_1.so
|
||||
- MODULE_2=/lib/echovault/modules/module_2.so
|
||||
- MODULE_1=/lib/echovault/modules/module_set/module_set.so
|
||||
- MODULE_2=/lib/echovault/modules/module_get/module_get.so
|
||||
ports:
|
||||
- "7485:7480"
|
||||
- "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.
|
||||
func (server *EchoVault) AddCommand(command CommandOptions) error {
|
||||
server.commandsRWMut.Lock()
|
||||
defer server.commandsRWMut.Unlock()
|
||||
// Check if command already exists
|
||||
for _, c := range server.commands {
|
||||
if strings.EqualFold(c.Command, command.Command) {
|
||||
@@ -398,6 +400,9 @@ func (server *EchoVault) ExecuteCommand(command ...string) ([]byte, error) {
|
||||
//
|
||||
// `command` - ...string.
|
||||
func (server *EchoVault) RemoveCommand(command ...string) {
|
||||
server.commandsRWMut.Lock()
|
||||
defer server.commandsRWMut.Unlock()
|
||||
|
||||
switch len(command) {
|
||||
case 1:
|
||||
// Remove command
|
||||
|
@@ -82,6 +82,7 @@ type EchoVault struct {
|
||||
}
|
||||
|
||||
// Holds the list of all commands supported by the echovault.
|
||||
commandsRWMut sync.RWMutex
|
||||
commands []internal.Command
|
||||
getCommands func() []internal.Command
|
||||
|
||||
@@ -133,6 +134,7 @@ func NewEchoVault(options ...func(echovault *EchoVault)) (*EchoVault, error) {
|
||||
store: make(map[string]internal.KeyData),
|
||||
keyLocks: make(map[string]*sync.RWMutex),
|
||||
keyCreationLock: &sync.Mutex{},
|
||||
commandsRWMut: sync.RWMutex{},
|
||||
commands: func() []internal.Command {
|
||||
var commands []internal.Command
|
||||
commands = append(commands, acl.Commands()...)
|
||||
@@ -161,8 +163,10 @@ func NewEchoVault(options ...func(echovault *EchoVault)) (*EchoVault, error) {
|
||||
// Load .so modules from config
|
||||
for _, path := range echovault.config.Modules {
|
||||
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
|
||||
|
@@ -25,6 +25,8 @@ import (
|
||||
)
|
||||
|
||||
func (server *EchoVault) getCommand(cmd string) (internal.Command, error) {
|
||||
server.commandsRWMut.RLock()
|
||||
defer server.commandsRWMut.RUnlock()
|
||||
for _, command := range server.commands {
|
||||
if strings.EqualFold(command.Command, cmd) {
|
||||
return command, nil
|
||||
@@ -52,6 +54,9 @@ func (server *EchoVault) getHandlerFuncParams(ctx context.Context, cmd []string,
|
||||
TakeSnapshot: server.takeSnapshot,
|
||||
GetLatestSnapshotTime: server.getLatestSnapshotTime,
|
||||
RewriteAOF: server.rewriteAOF,
|
||||
LoadModule: server.LoadModule,
|
||||
UnloadModule: server.UnloadModule,
|
||||
ListModules: server.ListModules,
|
||||
GetClock: server.getClock,
|
||||
GetPubSub: server.getPubSub,
|
||||
GetACL: server.getACL,
|
||||
|
@@ -3,23 +3,25 @@ package echovault
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/echovault/echovault/internal"
|
||||
"plugin"
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// TODO: Add godoc comment
|
||||
func (server *EchoVault) LoadModule(path string, args ...string) error {
|
||||
p, err := plugin.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("plugin open: %v", err)
|
||||
}
|
||||
|
||||
commandSymbol, err := p.Lookup("Command")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
command, ok := commandSymbol.(string)
|
||||
command, ok := commandSymbol.(*string)
|
||||
if !ok {
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
categories, ok := categoriesSymbol.([]string)
|
||||
categories, ok := categoriesSymbol.(*[]string)
|
||||
if !ok {
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
description, ok := descriptionSymbol.(string)
|
||||
description, ok := descriptionSymbol.(*string)
|
||||
if !ok {
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
sync, ok := syncSymbol.(bool)
|
||||
sync, ok := syncSymbol.(*bool)
|
||||
if !ok {
|
||||
return errors.New("sync symbol is not a bool")
|
||||
}
|
||||
|
||||
keyExtractionFuncSymbol, err := p.Lookup("KeyExtractionFunc")
|
||||
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))
|
||||
if !ok {
|
||||
@@ -62,7 +64,7 @@ func (server *EchoVault) LoadModule(path string, args ...string) error {
|
||||
|
||||
handlerFuncSymbol, err := p.Lookup("HandlerFunc")
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("handler func symbol: %v", err)
|
||||
}
|
||||
handlerFunc, ok := handlerFuncSymbol.(func(
|
||||
ctx context.Context,
|
||||
@@ -81,19 +83,22 @@ func (server *EchoVault) LoadModule(path string, args ...string) error {
|
||||
return errors.New("handler function has unexpected signature")
|
||||
}
|
||||
|
||||
server.commandsRWMut.Lock()
|
||||
defer server.commandsRWMut.Unlock()
|
||||
|
||||
server.commands = append(server.commands, internal.Command{
|
||||
Command: command,
|
||||
Command: *command,
|
||||
Module: path,
|
||||
Categories: func() []string {
|
||||
// Convert all the categories to lower case for uniformity
|
||||
cats := make([]string, len(categories))
|
||||
for i, cat := range categories {
|
||||
cats := make([]string, len(*categories))
|
||||
for i, cat := range *categories {
|
||||
cats[i] = strings.ToLower(cat)
|
||||
}
|
||||
return cats
|
||||
}(),
|
||||
Description: description,
|
||||
Sync: sync,
|
||||
Description: *description,
|
||||
Sync: *sync,
|
||||
SubCommands: make([]internal.SubCommand, 0),
|
||||
KeyExtractionFunc: func(cmd []string) (internal.KeyExtractionFuncResult, error) {
|
||||
readKeys, writeKeys, err := keyExtractionFunc(cmd, args...)
|
||||
@@ -125,8 +130,26 @@ func (server *EchoVault) LoadModule(path string, args ...string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: Add godoc comment
|
||||
func (server *EchoVault) UnloadModule(module string) {
|
||||
server.commandsRWMut.Lock()
|
||||
defer server.commandsRWMut.Unlock()
|
||||
server.commands = slices.DeleteFunc(server.commands, func(command internal.Command) bool {
|
||||
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,
|
||||
KeyExtractionFunc: func(cmd []string) (internal.KeyExtractionFuncResult, error) {
|
||||
return internal.KeyExtractionFuncResult{
|
||||
Channels: make([]string, 0),
|
||||
ReadKeys: make([]string, 0),
|
||||
WriteKeys: make([]string, 0),
|
||||
Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: make([]string, 0),
|
||||
}, nil
|
||||
},
|
||||
HandlerFunc: handleGetAllCommands,
|
||||
@@ -214,9 +212,7 @@ func Commands() []internal.Command {
|
||||
Sync: false,
|
||||
KeyExtractionFunc: func(cmd []string) (internal.KeyExtractionFuncResult, error) {
|
||||
return internal.KeyExtractionFuncResult{
|
||||
Channels: make([]string, 0),
|
||||
ReadKeys: make([]string, 0),
|
||||
WriteKeys: make([]string, 0),
|
||||
Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: make([]string, 0),
|
||||
}, nil
|
||||
},
|
||||
SubCommands: []internal.SubCommand{
|
||||
@@ -228,9 +224,7 @@ func Commands() []internal.Command {
|
||||
Sync: false,
|
||||
KeyExtractionFunc: func(cmd []string) (internal.KeyExtractionFuncResult, error) {
|
||||
return internal.KeyExtractionFuncResult{
|
||||
Channels: make([]string, 0),
|
||||
ReadKeys: make([]string, 0),
|
||||
WriteKeys: make([]string, 0),
|
||||
Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: make([]string, 0),
|
||||
}, nil
|
||||
},
|
||||
HandlerFunc: handleCommandDocs,
|
||||
@@ -243,9 +237,7 @@ func Commands() []internal.Command {
|
||||
Sync: false,
|
||||
KeyExtractionFunc: func(cmd []string) (internal.KeyExtractionFuncResult, error) {
|
||||
return internal.KeyExtractionFuncResult{
|
||||
Channels: make([]string, 0),
|
||||
ReadKeys: make([]string, 0),
|
||||
WriteKeys: make([]string, 0),
|
||||
Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: make([]string, 0),
|
||||
}, nil
|
||||
},
|
||||
HandlerFunc: handleCommandCount,
|
||||
@@ -259,9 +251,7 @@ Allows for filtering by ACL category or glob pattern.`,
|
||||
Sync: false,
|
||||
KeyExtractionFunc: func(cmd []string) (internal.KeyExtractionFuncResult, error) {
|
||||
return internal.KeyExtractionFuncResult{
|
||||
Channels: make([]string, 0),
|
||||
ReadKeys: make([]string, 0),
|
||||
WriteKeys: make([]string, 0),
|
||||
Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: make([]string, 0),
|
||||
}, nil
|
||||
},
|
||||
HandlerFunc: handleCommandList,
|
||||
@@ -276,9 +266,7 @@ Allows for filtering by ACL category or glob pattern.`,
|
||||
Sync: true,
|
||||
KeyExtractionFunc: func(cmd []string) (internal.KeyExtractionFuncResult, error) {
|
||||
return internal.KeyExtractionFuncResult{
|
||||
Channels: make([]string, 0),
|
||||
ReadKeys: make([]string, 0),
|
||||
WriteKeys: make([]string, 0),
|
||||
Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: make([]string, 0),
|
||||
}, nil
|
||||
},
|
||||
HandlerFunc: func(params internal.HandlerFuncParams) ([]byte, error) {
|
||||
@@ -296,9 +284,7 @@ Allows for filtering by ACL category or glob pattern.`,
|
||||
Sync: false,
|
||||
KeyExtractionFunc: func(cmd []string) (internal.KeyExtractionFuncResult, error) {
|
||||
return internal.KeyExtractionFuncResult{
|
||||
Channels: make([]string, 0),
|
||||
ReadKeys: make([]string, 0),
|
||||
WriteKeys: make([]string, 0),
|
||||
Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: make([]string, 0),
|
||||
}, nil
|
||||
},
|
||||
HandlerFunc: func(params internal.HandlerFuncParams) ([]byte, error) {
|
||||
@@ -317,9 +303,7 @@ Allows for filtering by ACL category or glob pattern.`,
|
||||
Sync: false,
|
||||
KeyExtractionFunc: func(cmd []string) (internal.KeyExtractionFuncResult, error) {
|
||||
return internal.KeyExtractionFuncResult{
|
||||
Channels: make([]string, 0),
|
||||
ReadKeys: make([]string, 0),
|
||||
WriteKeys: make([]string, 0),
|
||||
Channels: make([]string, 0), ReadKeys: make([]string, 0), WriteKeys: make([]string, 0),
|
||||
}, nil
|
||||
},
|
||||
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
|
||||
},
|
||||
},
|
||||
{
|
||||
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
|
||||
RewriteAOF func() error
|
||||
GetLatestSnapshotTime func() int64
|
||||
LoadModule func(path string, args ...string) error
|
||||
UnloadModule func(module string)
|
||||
ListModules func() []string
|
||||
}
|
||||
|
||||
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