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:
Kelvin Clement Mwinuka
2024-05-03 11:57:21 +08:00
parent 276ca0fb63
commit 0f6ae1c8ac
14 changed files with 289 additions and 56 deletions

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -82,6 +82,7 @@ type EchoVault struct {
} }
// Holds the list of all commands supported by the echovault. // Holds the list of all commands supported by the echovault.
commandsRWMut sync.RWMutex
commands []internal.Command commands []internal.Command
getCommands func() []internal.Command getCommands func() []internal.Command
@@ -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

View File

@@ -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,

View File

@@ -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
}

View File

@@ -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
},
},
},
},
} }
} }

View File

@@ -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)

View File

@@ -1 +0,0 @@
package modules

View File

@@ -1 +0,0 @@
package modules

View 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
}

Binary file not shown.

View 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
}

Binary file not shown.