| 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 |
| } |