mirror of
https://github.com/EchoVault/SugarDB.git
synced 2025-10-05 16:06:57 +08:00
175 lines
4.4 KiB
Go
175 lines
4.4 KiB
Go
// Copyright 2024 Kelvin Clement Mwinuka
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package raft
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"github.com/echovault/echovault/internal"
|
|
"github.com/echovault/echovault/internal/config"
|
|
"github.com/hashicorp/raft"
|
|
"io"
|
|
"log"
|
|
"net"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type FSMOpts struct {
|
|
Config config.Config
|
|
GetState func() map[string]internal.KeyData
|
|
GetCommand func(command string) (internal.Command, error)
|
|
SetValues func(ctx context.Context, entries map[string]interface{}) error
|
|
SetExpiry func(ctx context.Context, key string, expire time.Time, touch bool)
|
|
DeleteKey func(key string) error
|
|
StartSnapshot func()
|
|
FinishSnapshot func()
|
|
SetLatestSnapshotTime func(msec int64)
|
|
GetHandlerFuncParams func(ctx context.Context, cmd []string, conn *net.Conn) internal.HandlerFuncParams
|
|
}
|
|
|
|
type FSM struct {
|
|
options FSMOpts
|
|
}
|
|
|
|
func NewFSM(opts FSMOpts) raft.FSM {
|
|
return raft.FSM(&FSM{
|
|
options: opts,
|
|
})
|
|
}
|
|
|
|
// Apply Implements raft.FSM interface
|
|
func (fsm *FSM) Apply(log *raft.Log) interface{} {
|
|
switch log.Type {
|
|
default:
|
|
// No-Op
|
|
case raft.LogCommand:
|
|
var request internal.ApplyRequest
|
|
|
|
if err := json.Unmarshal(log.Data, &request); err != nil {
|
|
return internal.ApplyResponse{
|
|
Error: err,
|
|
Response: nil,
|
|
}
|
|
}
|
|
|
|
ctx := context.WithValue(context.Background(), internal.ContextServerID("ServerID"), request.ServerID)
|
|
ctx = context.WithValue(ctx, internal.ContextConnID("ConnectionID"), request.ConnectionID)
|
|
|
|
switch strings.ToLower(request.Type) {
|
|
default:
|
|
return internal.ApplyResponse{
|
|
Error: fmt.Errorf("unsupported raft command type %s", request.Type),
|
|
Response: nil,
|
|
}
|
|
|
|
case "delete-key":
|
|
if err := fsm.options.DeleteKey(request.Key); err != nil {
|
|
return internal.ApplyResponse{
|
|
Error: err,
|
|
Response: nil,
|
|
}
|
|
}
|
|
return internal.ApplyResponse{
|
|
Error: nil,
|
|
Response: []byte("OK"),
|
|
}
|
|
|
|
case "command":
|
|
// Handle command
|
|
command, err := fsm.options.GetCommand(request.CMD[0])
|
|
if err != nil {
|
|
return internal.ApplyResponse{
|
|
Error: err,
|
|
Response: nil,
|
|
}
|
|
}
|
|
|
|
handler := command.HandlerFunc
|
|
|
|
sc, err := internal.GetSubCommand(command, request.CMD)
|
|
if err != nil {
|
|
return internal.ApplyResponse{
|
|
Error: err,
|
|
Response: nil,
|
|
}
|
|
}
|
|
subCommand, ok := sc.(internal.SubCommand)
|
|
if ok {
|
|
handler = subCommand.HandlerFunc
|
|
}
|
|
|
|
if res, err := handler(fsm.options.GetHandlerFuncParams(ctx, request.CMD, nil)); err != nil {
|
|
return internal.ApplyResponse{
|
|
Error: err,
|
|
Response: nil,
|
|
}
|
|
} else {
|
|
return internal.ApplyResponse{
|
|
Error: nil,
|
|
Response: res,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Snapshot implements raft.FSM interface
|
|
func (fsm *FSM) Snapshot() (raft.FSMSnapshot, error) {
|
|
return NewFSMSnapshot(SnapshotOpts{
|
|
config: fsm.options.Config,
|
|
startSnapshot: fsm.options.StartSnapshot,
|
|
finishSnapshot: fsm.options.FinishSnapshot,
|
|
setLatestSnapshotTime: fsm.options.SetLatestSnapshotTime,
|
|
data: fsm.options.GetState(),
|
|
}), nil
|
|
}
|
|
|
|
// Restore implements raft.FSM interface
|
|
func (fsm *FSM) Restore(snapshot io.ReadCloser) error {
|
|
b, err := io.ReadAll(snapshot)
|
|
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
return err
|
|
}
|
|
|
|
data := internal.SnapshotObject{
|
|
State: make(map[string]internal.KeyData),
|
|
LatestSnapshotMilliseconds: 0,
|
|
}
|
|
|
|
if err = json.Unmarshal(b, &data); err != nil {
|
|
log.Fatal(err)
|
|
return err
|
|
}
|
|
|
|
// Set state
|
|
ctx := context.Background()
|
|
for k, v := range internal.FilterExpiredKeys(time.Now(), data.State) {
|
|
if err = fsm.options.SetValues(ctx, map[string]interface{}{k: v.Value}); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
fsm.options.SetExpiry(ctx, k, v.ExpireAt, false)
|
|
}
|
|
// Set latest snapshot milliseconds
|
|
fsm.options.SetLatestSnapshotTime(data.LatestSnapshotMilliseconds)
|
|
|
|
return nil
|
|
}
|