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 }