blob: 218abaeffaa006fd7d7fe754d5e1420bb4eb5caf [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
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"
"fmt"
"os"
"strconv"
"strings"
)
const (
addLocal = iota
addRemote
setDevTable
linkLocal
linkRemote
)
type syncCommand struct {
cmd int
objID ObjId
version Version
parents []Version
logrec string
devID DeviceId
genVec GenVector
txID TxId
txCount uint32
deleted bool
}
func strToVersion(verStr string) (Version, error) {
ver, err := strconv.ParseUint(verStr, 10, 64)
if err != nil {
return 0, err
}
return Version(ver), 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 := 9
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 []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)
}
}
txID, err := strToTxId(args[6])
if err != nil {
return nil, fmt.Errorf("%s:%d: invalid TxId: %s", file, lineno, args[6])
}
txCount, err := strconv.ParseUint(args[7], 10, 32)
if err != nil {
return nil, fmt.Errorf("%s:%d: invalid tx count: %s", file, lineno, args[7])
}
del, err := strconv.ParseBool(args[8])
if err != nil {
return nil, fmt.Errorf("%s:%d: invalid deleted bit: %s", file, lineno, args[8])
}
cmd := syncCommand{
version: version,
parents: parents,
logrec: args[5],
txID: txID,
txCount: uint32(txCount),
deleted: del,
}
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: []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:
err = dag.addNode(cmd.objID, cmd.version, false, cmd.deleted, cmd.parents, cmd.logrec, NoTxId)
if err != nil {
return fmt.Errorf("cannot add local node %v:%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 %v:%d in DAG: %v", cmd.objID, cmd.version, err)
}
dag.flush()
case addRemote:
err = dag.addNode(cmd.objID, cmd.version, true, cmd.deleted, cmd.parents, cmd.logrec, NoTxId)
if err != nil {
return fmt.Errorf("cannot add remote node %v:%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 %v:%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 %v:%d: %v",
cmd.parents[0], cmd.objID, cmd.version, err)
}
dag.flush()
}
}
return nil
}