blob: 02cd561dabc7711d7895635d40218ac4ee860bf0 [file] [log] [blame]
package vsync
// Used to ease the setup of Veyron Sync test scenarios.
// Parses a sync command file and returns a vector of commands to execute.
//
// Used by different test replay engines:
// - dagReplayCommands() executes the parsed commands at the DAG API level.
// - logReplayCommands() executes the parsed commands at the Log API level.
import (
"bufio"
"encoding/binary"
"fmt"
"os"
"strconv"
"strings"
"veyron2/storage"
)
const (
addLocal = iota
addRemote
setDevTable
linkLocal
linkRemote
)
type syncCommand struct {
cmd int
objID storage.ID
version storage.Version
parents []storage.Version
logrec string
devID DeviceID
genVec GenVector
}
func strToObjID(objStr string) (storage.ID, error) {
var objID storage.ID
id, err := strconv.ParseUint(objStr, 10, 64)
if err != nil {
return objID, err
}
idbuf := make([]byte, len(objID))
if binary.PutUvarint(idbuf, id) == 0 {
return objID, fmt.Errorf("cannot store ID %d into a binary buffer", id)
}
for i := 0; i < len(objID); i++ {
objID[i] = idbuf[i]
}
return objID, nil
}
func strToVersion(verStr string) (storage.Version, error) {
ver, err := strconv.ParseUint(verStr, 10, 64)
if err != nil {
return 0, err
}
return storage.Version(ver), nil
}
func strToGenID(genIDStr string) (GenID, error) {
id, err := strconv.ParseUint(genIDStr, 10, 64)
if err != nil {
return 0, err
}
return GenID(id), nil
}
func parseSyncCommands(file string) ([]syncCommand, error) {
cmds := []syncCommand{}
sf, err := os.Open("testdata/" + file)
if err != nil {
return nil, err
}
defer sf.Close()
scanner := bufio.NewScanner(sf)
lineno := 0
for scanner.Scan() {
lineno++
line := strings.TrimSpace(scanner.Text())
if line == "" || line[0] == '#' {
continue
}
args := strings.Split(line, "|")
nargs := len(args)
switch args[0] {
case "addl", "addr":
expNargs := 6
if nargs != expNargs {
return nil, fmt.Errorf("%s:%d: need %d args instead of %d", file, lineno, expNargs, nargs)
}
version, err := strToVersion(args[2])
if err != nil {
return nil, fmt.Errorf("%s:%d: invalid version: %s", file, lineno, args[2])
}
var parents []storage.Version
for i := 3; i <= 4; i++ {
if args[i] != "" {
pver, err := strToVersion(args[i])
if err != nil {
return nil, fmt.Errorf("%s:%d: invalid parent: %s", file, lineno, args[i])
}
parents = append(parents, pver)
}
}
cmd := syncCommand{version: version, parents: parents, logrec: args[5]}
if args[0] == "addl" {
cmd.cmd = addLocal
} else {
cmd.cmd = addRemote
}
if cmd.objID, err = strToObjID(args[1]); err != nil {
return nil, fmt.Errorf("%s:%d: invalid object ID: %s", file, lineno, args[1])
}
cmds = append(cmds, cmd)
case "setdev":
expNargs := 3
if nargs != expNargs {
return nil, fmt.Errorf("%s:%d: need %d args instead of %d", file, lineno, expNargs, nargs)
}
genVec := make(GenVector)
for _, elem := range strings.Split(args[2], ",") {
kv := strings.Split(elem, ":")
if len(kv) != 2 {
return nil, fmt.Errorf("%s:%d: invalid gen vector key/val: %s", file, lineno, elem)
}
genID, err := strToGenID(kv[1])
if err != nil {
return nil, fmt.Errorf("%s:%d: invalid gen ID: %s", file, lineno, kv[1])
}
genVec[DeviceID(kv[0])] = genID
}
cmd := syncCommand{cmd: setDevTable, devID: DeviceID(args[1]), genVec: genVec}
cmds = append(cmds, cmd)
case "linkl", "linkr":
expNargs := 6
if nargs != expNargs {
return nil, fmt.Errorf("%s:%d: need %d args instead of %d", file, lineno, expNargs, nargs)
}
version, err := strToVersion(args[2])
if err != nil {
return nil, fmt.Errorf("%s:%d: invalid version: %s", file, lineno, args[2])
}
if args[3] == "" {
return nil, fmt.Errorf("%s:%d: parent (to-node) version not specified", file, lineno)
}
if args[4] != "" {
return nil, fmt.Errorf("%s:%d: cannot specify a 2nd parent (to-node): %s", file, lineno, args[4])
}
parent, err := strToVersion(args[3])
if err != nil {
return nil, fmt.Errorf("%s:%d: invalid parent (to-node) version: %s", file, lineno, args[3])
}
cmd := syncCommand{version: version, parents: []storage.Version{parent}, logrec: args[5]}
if args[0] == "linkl" {
cmd.cmd = linkLocal
} else {
cmd.cmd = linkRemote
}
if cmd.objID, err = strToObjID(args[1]); err != nil {
return nil, fmt.Errorf("%s:%d: invalid object ID: %s", file, lineno, args[1])
}
cmds = append(cmds, cmd)
default:
return nil, fmt.Errorf("%s:%d: invalid operation: %s", file, lineno, args[0])
}
}
err = scanner.Err()
return cmds, err
}
func dagReplayCommands(dag *dag, syncfile string) error {
cmds, err := parseSyncCommands(syncfile)
if err != nil {
return err
}
for _, cmd := range cmds {
switch cmd.cmd {
case addLocal:
if err = dag.addNode(cmd.objID, cmd.version, false, cmd.parents, cmd.logrec); err != nil {
return fmt.Errorf("cannot add local node %d:%d to DAG: %v", cmd.objID, cmd.version, err)
}
if err := dag.moveHead(cmd.objID, cmd.version); err != nil {
return fmt.Errorf("cannot move head to %d:%d in DAG: %v", cmd.objID, cmd.version, err)
}
dag.flush()
case addRemote:
if err = dag.addNode(cmd.objID, cmd.version, true, cmd.parents, cmd.logrec); err != nil {
return fmt.Errorf("cannot add remote node %d:%d to DAG: %v", cmd.objID, cmd.version, err)
}
dag.flush()
case linkLocal:
if err = dag.addParent(cmd.objID, cmd.version, cmd.parents[0], false); err != nil {
return fmt.Errorf("cannot add local parent %d to DAG node %d:%d: %v",
cmd.parents[0], cmd.objID, cmd.version, err)
}
dag.flush()
case linkRemote:
if err = dag.addParent(cmd.objID, cmd.version, cmd.parents[0], true); err != nil {
return fmt.Errorf("cannot add remote parent %d to DAG node %d:%d: %v",
cmd.parents[0], cmd.objID, cmd.version, err)
}
dag.flush()
}
}
return nil
}