github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/helper/raftutil/state.go (about)

     1  package raftutil
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"os"
     8  	"path/filepath"
     9  	"strings"
    10  	"time"
    11  
    12  	"github.com/hashicorp/go-msgpack/codec"
    13  	"github.com/hashicorp/nomad/nomad/structs"
    14  	"github.com/hashicorp/raft"
    15  	raftboltdb "github.com/hashicorp/raft-boltdb/v2"
    16  	"go.etcd.io/bbolt"
    17  )
    18  
    19  var (
    20  	errAlreadyOpen = errors.New("unable to open raft logs that are in use")
    21  )
    22  
    23  // RaftStateInfo returns info about the nomad state, as found in the passed data-dir directory
    24  func RaftStateInfo(p string) (store *raftboltdb.BoltStore, firstIdx uint64, lastIdx uint64, err error) {
    25  	opts := raftboltdb.Options{
    26  		Path: p,
    27  		BoltOptions: &bbolt.Options{
    28  			ReadOnly: true,
    29  			Timeout:  1 * time.Second,
    30  		},
    31  	}
    32  	s, err := raftboltdb.New(opts)
    33  	if err != nil {
    34  		if strings.HasSuffix(err.Error(), "timeout") {
    35  			return nil, 0, 0, errAlreadyOpen
    36  		}
    37  		return nil, 0, 0, fmt.Errorf("failed to open raft logs: %v", err)
    38  	}
    39  
    40  	firstIdx, err = s.FirstIndex()
    41  	if err != nil {
    42  		return nil, 0, 0, fmt.Errorf("failed to fetch first index: %v", err)
    43  	}
    44  
    45  	lastIdx, err = s.LastIndex()
    46  	if err != nil {
    47  		return nil, 0, 0, fmt.Errorf("failed to fetch last index: %v", err)
    48  	}
    49  
    50  	return s, firstIdx, lastIdx, nil
    51  }
    52  
    53  // LogEntries reads the raft logs found in the data directory found at
    54  // the path `p`, and returns a channel of logs, and a channel of
    55  // warnings. If opening the raft state returns an error, both channels
    56  // will be nil.
    57  func LogEntries(p string) (<-chan interface{}, <-chan error, error) {
    58  	store, firstIdx, lastIdx, err := RaftStateInfo(p)
    59  	if err != nil {
    60  		return nil, nil, fmt.Errorf("failed to open raft logs: %v", err)
    61  	}
    62  
    63  	entries := make(chan interface{})
    64  	warnings := make(chan error)
    65  
    66  	go func() {
    67  		defer store.Close()
    68  		defer close(entries)
    69  		for i := firstIdx; i <= lastIdx; i++ {
    70  			var e raft.Log
    71  			err := store.GetLog(i, &e)
    72  			if err != nil {
    73  				warnings <- fmt.Errorf(
    74  					"failed to read log entry at index %d (firstIdx: %d, lastIdx: %d): %v",
    75  					i, firstIdx, lastIdx, err)
    76  				continue
    77  			}
    78  
    79  			entry, err := decode(&e)
    80  			if err != nil {
    81  				warnings <- fmt.Errorf(
    82  					"failed to decode log entry at index %d: %v", i, err)
    83  				continue
    84  			}
    85  
    86  			entries <- entry
    87  		}
    88  	}()
    89  
    90  	return entries, warnings, nil
    91  }
    92  
    93  type logMessage struct {
    94  	LogType string
    95  	Term    uint64
    96  	Index   uint64
    97  
    98  	CommandType           string      `json:",omitempty"`
    99  	IgnoreUnknownTypeFlag bool        `json:",omitempty"`
   100  	Body                  interface{} `json:",omitempty"`
   101  }
   102  
   103  func decode(e *raft.Log) (*logMessage, error) {
   104  	m := &logMessage{
   105  		LogType: logTypes[e.Type],
   106  		Term:    e.Term,
   107  		Index:   e.Index,
   108  	}
   109  
   110  	if m.LogType == "" {
   111  		m.LogType = fmt.Sprintf("%d", e.Type)
   112  	}
   113  
   114  	var data []byte
   115  	if e.Type == raft.LogCommand {
   116  		if len(e.Data) == 0 {
   117  			return nil, fmt.Errorf("command did not include data")
   118  		}
   119  
   120  		msgType := structs.MessageType(e.Data[0])
   121  
   122  		m.CommandType = commandName(msgType & ^structs.IgnoreUnknownTypeFlag)
   123  		m.IgnoreUnknownTypeFlag = (msgType & structs.IgnoreUnknownTypeFlag) != 0
   124  
   125  		data = e.Data[1:]
   126  	} else {
   127  		data = e.Data
   128  	}
   129  
   130  	if len(data) != 0 {
   131  		decoder := codec.NewDecoder(bytes.NewReader(data), structs.MsgpackHandle)
   132  
   133  		var v interface{}
   134  		var err error
   135  		if m.CommandType == commandName(structs.JobBatchDeregisterRequestType) {
   136  			var vr structs.JobBatchDeregisterRequest
   137  			err = decoder.Decode(&vr)
   138  			v = jsonifyJobBatchDeregisterRequest(&vr)
   139  		} else {
   140  			var vr interface{}
   141  			err = decoder.Decode(&vr)
   142  			v = vr
   143  		}
   144  
   145  		if err != nil {
   146  			fmt.Fprintf(os.Stderr, "failed to decode log entry at index %d: failed to decode body of %v.%v %v\n", e.Index, e.Type, m.CommandType, err)
   147  			v = "FAILED TO DECODE DATA"
   148  		}
   149  		fixTime(v)
   150  		m.Body = v
   151  	}
   152  
   153  	return m, nil
   154  }
   155  
   156  // jsonifyJobBatchDeregisterRequest special case JsonBatchDeregisterRequest object
   157  // as the actual type is not json friendly.
   158  func jsonifyJobBatchDeregisterRequest(v *structs.JobBatchDeregisterRequest) interface{} {
   159  	var data struct {
   160  		Jobs  map[string]*structs.JobDeregisterOptions
   161  		Evals []*structs.Evaluation
   162  		structs.WriteRequest
   163  	}
   164  	data.Evals = v.Evals
   165  	data.WriteRequest = v.WriteRequest
   166  
   167  	data.Jobs = make(map[string]*structs.JobDeregisterOptions, len(v.Jobs))
   168  	if len(v.Jobs) != 0 {
   169  		for k, v := range v.Jobs {
   170  			data.Jobs[k.Namespace+"."+k.ID] = v
   171  		}
   172  	}
   173  	return data
   174  }
   175  
   176  var logTypes = map[raft.LogType]string{
   177  	raft.LogCommand:              "LogCommand",
   178  	raft.LogNoop:                 "LogNoop",
   179  	raft.LogAddPeerDeprecated:    "LogAddPeerDeprecated",
   180  	raft.LogRemovePeerDeprecated: "LogRemovePeerDeprecated",
   181  	raft.LogBarrier:              "LogBarrier",
   182  	raft.LogConfiguration:        "LogConfiguration",
   183  }
   184  
   185  func commandName(mt structs.MessageType) string {
   186  	n := msgTypeNames[mt]
   187  	if n != "" {
   188  		return n
   189  	}
   190  
   191  	return fmt.Sprintf("%v", mt)
   192  }
   193  
   194  // FindRaftFile finds raft.db and returns path
   195  func FindRaftFile(p string) (raftpath string, err error) {
   196  	// Try known locations before traversal to avoid walking deep structure
   197  	if _, err = os.Stat(filepath.Join(p, "server", "raft", "raft.db")); err == nil {
   198  		raftpath = filepath.Join(p, "server", "raft", "raft.db")
   199  	} else if _, err = os.Stat(filepath.Join(p, "raft", "raft.db")); err == nil {
   200  		raftpath = filepath.Join(p, "raft", "raft.db")
   201  	} else if _, err = os.Stat(filepath.Join(p, "raft.db")); err == nil {
   202  		raftpath = filepath.Join(p, "raft.db")
   203  	} else if _, err = os.Stat(p); err == nil && filepath.Ext(p) == ".db" {
   204  		// Also accept path to .db file
   205  		raftpath = p
   206  	} else {
   207  		raftpath, err = FindFileInPath("raft.db", p)
   208  	}
   209  
   210  	if err != nil {
   211  		return "", err
   212  	}
   213  
   214  	return raftpath, nil
   215  }
   216  
   217  // FindRaftDir finds raft.db and returns parent directory path
   218  func FindRaftDir(p string) (raftpath string, err error) {
   219  	raftpath, err = FindRaftFile(p)
   220  	if err != nil {
   221  		return "", err
   222  	}
   223  
   224  	if raftpath == "" {
   225  		return "", fmt.Errorf("failed to find raft dir in %s", p)
   226  	}
   227  
   228  	return filepath.Dir(raftpath), nil
   229  }
   230  
   231  // FindFileInPath searches for file in path p
   232  func FindFileInPath(file string, p string) (path string, err error) {
   233  	// Define walk function to find file
   234  	walkFn := func(walkPath string, info os.FileInfo, err error) error {
   235  		if err != nil {
   236  			return err
   237  		}
   238  
   239  		if filepath.Base(walkPath) == file {
   240  			path = walkPath
   241  		}
   242  		return nil
   243  	}
   244  
   245  	// Walk path p looking for file
   246  	walkErr := filepath.Walk(p, walkFn)
   247  	if walkErr != nil {
   248  		return "", walkErr
   249  	}
   250  
   251  	if path == "" {
   252  		return "", fmt.Errorf("File %s not found in path %s", file, p)
   253  	}
   254  
   255  	return path, nil
   256  }