Skip to content

Commit

Permalink
*: add Model, Operation and Checker (#20)
Browse files Browse the repository at this point in the history
Model defines an interface of a data object and Checker
defines an interface of a checker, together they privode an
abstract of linearizability check formwork.

It also decouples Porcupine from Chaos. We can add more checkers
in the future.
  • Loading branch information
overvenus authored and siddontang committed Nov 22, 2018
1 parent 9ccc0f0 commit e7464e1
Show file tree
Hide file tree
Showing 20 changed files with 645 additions and 303 deletions.
1 change: 1 addition & 0 deletions Porcupine-LICENSE
3 changes: 1 addition & 2 deletions cmd/rawkv/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ var (
clientCase = flag.String("case", "register", "client test case, like bank,multi_bank")
historyFile = flag.String("history", "./history.log", "history file")
nemesises = flag.String("nemesis", "", "nemesis, seperated by name, like random_kill,all_kill")
verifyNames = flag.String("verifiers", "", "verifier names, seperate by comma, register")
)

func main() {
Expand All @@ -41,7 +40,7 @@ func main() {
suit := util.Suit{
Config: cfg,
ClientCreator: creator,
VerifyNames: *verifyNames,
ModelNames: "register",
Nemesises: *nemesises,
}
suit.Run()
Expand Down
4 changes: 2 additions & 2 deletions cmd/tidb/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ var (
clientCase = flag.String("case", "bank", "client test case, like bank,multi_bank")
historyFile = flag.String("history", "./history.log", "history file")
nemesises = flag.String("nemesis", "", "nemesis, seperated by name, like random_kill,all_kill")
verifyNames = flag.String("verifiers", "", "verifier names, seperate by comma, tidb_bank,tidb_bank_tso")
modelNames = flag.String("models", "", "model names, seperate by comma, tidb_bank,tidb_bank_tso")
)

func main() {
Expand All @@ -43,7 +43,7 @@ func main() {
suit := util.Suit{
Config: cfg,
ClientCreator: creator,
VerifyNames: *verifyNames,
ModelNames: *modelNames,
Nemesises: *nemesises,
}
suit.Run()
Expand Down
3 changes: 1 addition & 2 deletions cmd/txnkv/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ var (
clientCase = flag.String("case", "register", "client test case, like bank,multi_bank")
historyFile = flag.String("history", "./history.log", "history file")
nemesises = flag.String("nemesis", "", "nemesis, seperated by name, like random_kill,all_kill")
verifyNames = flag.String("verifiers", "", "verifier names, seperate by comma, txnkv_register")
)

func main() {
Expand All @@ -41,7 +40,7 @@ func main() {
suit := util.Suit{
Config: cfg,
ClientCreator: creator,
VerifyNames: *verifyNames,
ModelNames: "register",
Nemesises: *nemesises,
}
suit.Run()
Expand Down
6 changes: 3 additions & 3 deletions cmd/util/suit.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ import (
type Suit struct {
control.Config
core.ClientCreator
// verifier names, seperate by comma.
VerifyNames string
// model names, seperate by comma.
ModelNames string
// nemesis, seperated by comma.
Nemesises string
}
Expand Down Expand Up @@ -61,5 +61,5 @@ func (suit *Suit) Run() {

c.Run()

verify.Verify(ctx, suit.Config.History, suit.VerifyNames)
verify.Verify(ctx, suit.Config.History, suit.ModelNames)
}
2 changes: 1 addition & 1 deletion cmd/verifier/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (

var (
historyFile = flag.String("history", "./history.log", "history file")
names = flag.String("names", "", "verifier names, seperate by comma")
names = flag.String("names", "", "model names, seperate by comma")
pprofAddr = flag.String("pprof", "0.0.0.0:6060", "Pprof address")
)

Expand Down
47 changes: 33 additions & 14 deletions cmd/verifier/verify/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,47 +6,66 @@ import (
"strings"

"github.com/siddontang/chaos/db/tidb"
"github.com/siddontang/chaos/pkg/check/porcupine"
"github.com/siddontang/chaos/pkg/core"
"github.com/siddontang/chaos/pkg/history"
"github.com/siddontang/chaos/pkg/model"
)

// Verify creates the verifier from verifer_names and verfies the history file.
func Verify(ctx context.Context, historyFile string, verfier_names string) {
var verifieres []history.Verifier
type suit struct {
checker core.Checker
model core.Model
parser history.RecordParser
}

// Verify creates the verifier from model name and verfies the history file.
func Verify(ctx context.Context, historyFile string, modelNames string) {
var suits []suit

for _, name := range strings.Split(verfier_names, ",") {
var verifier history.Verifier
for _, name := range strings.Split(modelNames, ",") {
s := suit{}
switch name {
case "tidb_bank":
verifier = tidb.BankVerifier{}
s.model, s.parser, s.checker = tidb.BankModel(), tidb.BankParser(), porcupine.Checker{}
case "tidb_bank_tso":
verifier = tidb.BankTsoVerifier{}
// Actually we can omit BankModel, since BankTsoChecker does not require a Model.
s.model, s.parser, s.checker = tidb.BankModel(), tidb.BankParser(), tidb.BankTsoChecker()
case "register":
verifier = model.RegisterVerifier{}
s.model, s.parser, s.checker = model.RegisterModel(), model.RegisterParser(), porcupine.Checker{}
case "":
continue
default:
log.Printf("%s is not supported", name)
continue
}

verifieres = append(verifieres, verifier)
suits = append(suits, s)
}

childCtx, cancel := context.WithCancel(ctx)

go func() {
for _, verifier := range verifieres {
log.Printf("begin to check with %s", verifier.Name())
ok, err := verifier.Verify(historyFile)
for _, suit := range suits {
log.Printf("begin to check %s with %s", suit.model.Name(), suit.checker.Name())
ops, err := history.ReadHistory(historyFile, suit.parser)
if err != nil {
log.Fatalf("read history failed %v", err)
}

ops, err = history.CompleteOperations(ops, suit.parser)
if err != nil {
log.Fatalf("complete history failed %v", err)
}

ok, err := suit.checker.Check(suit.model, ops)
if err != nil {
log.Fatalf("verify history failed %v", err)
}

if !ok {
log.Fatalf("%s: history %s is not linearizable", verifier.Name(), historyFile)
log.Fatalf("%s: history %s is not linearizable", suit.model.Name(), historyFile)
} else {
log.Printf("%s: history %s is linearizable", verifier.Name(), historyFile)
log.Printf("%s: history %s is linearizable", suit.model.Name(), historyFile)
}
}

Expand Down
154 changes: 74 additions & 80 deletions db/tidb/bank.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@ import (
"time"

"github.com/anishathalye/porcupine"
pchecker "github.com/siddontang/chaos/pkg/check/porcupine"
"github.com/siddontang/chaos/pkg/core"
"github.com/siddontang/chaos/pkg/history"

// use mysql
_ "github.com/go-sql-driver/mysql"
"github.com/siddontang/chaos/pkg/core"
"github.com/siddontang/chaos/pkg/history"
)

const (
Expand Down Expand Up @@ -197,14 +198,6 @@ func (r bankResponse) IsUnknown() bool {
return r.Unknown
}

func newBankEvent(v interface{}, id uint) porcupine.Event {
if _, ok := v.(bankRequest); ok {
return porcupine.Event{Kind: porcupine.CallEvent, Value: v, Id: id}
}

return porcupine.Event{Kind: porcupine.ReturnEvent, Value: v, Id: id}
}

func balancesEqual(a, b []int64) bool {
if len(a) != len(b) {
return false
Expand All @@ -219,54 +212,67 @@ func balancesEqual(a, b []int64) bool {
return true
}

func getBankModel(n int) porcupine.Model {
return porcupine.Model{
Init: func() interface{} {
v := make([]int64, n)
for i := 0; i < n; i++ {
v[i] = initBalance
}
return v
},
Step: func(state interface{}, input interface{}, output interface{}) (bool, interface{}) {
st := state.([]int64)
inp := input.(bankRequest)
out := output.(bankResponse)

if inp.Op == 0 {
// read
ok := out.Unknown || balancesEqual(st, out.Balances)
return ok, state
}
type bank struct {
accountNum int
}

// for transfer
if !out.Ok && !out.Unknown {
return true, state
}
func (b bank) Init() interface{} {
v := make([]int64, b.accountNum)
for i := 0; i < b.accountNum; i++ {
v[i] = initBalance
}
return v
}

func (bank) Step(state interface{}, input interface{}, output interface{}) (bool, interface{}) {
st := state.([]int64)
inp := input.(bankRequest)
out := output.(bankResponse)

newSt := append([]int64{}, st...)
newSt[inp.From] -= inp.Amount
newSt[inp.To] += inp.Amount
return out.Ok || out.Unknown, newSt
},
if inp.Op == 0 {
// read
ok := out.Unknown || balancesEqual(st, out.Balances)
return ok, state
}

Equal: func(state1, state2 interface{}) bool {
st1 := state1.([]int64)
st2 := state2.([]int64)
return balancesEqual(st1, st2)
},
// for transfer
if !out.Ok && !out.Unknown {
return true, state
}

newSt := append([]int64{}, st...)
newSt[inp.From] -= inp.Amount
newSt[inp.To] += inp.Amount
return out.Ok || out.Unknown, newSt
}

func (bank) Equal(state1, state2 interface{}) bool {
st1 := state1.([]int64)
st2 := state2.([]int64)
return balancesEqual(st1, st2)
}

func (bank) Name() string {
return "tidb_bank"
}

type bankParser struct {
// BankModel is the model of bank in TiDB
func BankModel() core.Model {
return bank{
accountNum: accountNum,
}
}

type bankParser struct{}

// OnRequest impls history.RecordParser.OnRequest
func (p bankParser) OnRequest(data json.RawMessage) (interface{}, error) {
r := bankRequest{}
err := json.Unmarshal(data, &r)
return r, err
}

// OnResponse impls history.RecordParser.OnRequest
func (p bankParser) OnResponse(data json.RawMessage) (interface{}, error) {
r := bankResponse{}
err := json.Unmarshal(data, &r)
Expand All @@ -276,10 +282,16 @@ func (p bankParser) OnResponse(data json.RawMessage) (interface{}, error) {
return r, err
}

// OnNoopResponse impls history.RecordParser.OnRequest
func (p bankParser) OnNoopResponse() interface{} {
return bankResponse{Unknown: true}
}

// BankParser parses a history of bank operations.
func BankParser() history.RecordParser {
return bankParser{}
}

// BankClientCreator creates a bank test client for tidb.
type BankClientCreator struct {
}
Expand All @@ -291,26 +303,6 @@ func (BankClientCreator) Create(node string) core.Client {
}
}

// BankVerifier verifies the bank history.
type BankVerifier struct {
}

// Verify verifies the bank history.
func (BankVerifier) Verify(historyFile string) (bool, error) {
return history.IsLinearizable(historyFile, getBankModel(accountNum), bankParser{})
}

// Name returns the name of the verifier.
func (BankVerifier) Name() string {
return "bank_verifier"
}

// BankTsoVerifier verifies the bank history.
// Unlike BankVerifier using porcupine, it uses a direct way because we know every timestamp of the transaction.
// So we can order all transactions with timetamp and replay them.
type BankTsoVerifier struct {
}

type tsoEvent struct {
Tso uint64
Op int
Expand Down Expand Up @@ -349,15 +341,7 @@ func (s tsoEvents) Len() int { return len(s) }
func (s tsoEvents) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s tsoEvents) Less(i, j int) bool { return s[i].Tso < s[j].Tso }

func parseTsoEvents(historyFile string) (tsoEvents, error) {
events, err := history.ParseEvents(historyFile, bankParser{})
if err != nil {
return nil, err
}

return generateTsoEvents(events), nil
}

// TODO: remove porcupine dependence.
func generateTsoEvents(events []porcupine.Event) tsoEvents {
tEvents := make(tsoEvents, 0, len(events))

Expand Down Expand Up @@ -554,17 +538,27 @@ func verifyTsoEvents(events tsoEvents) bool {
return true
}

// Verify verifes the bank history.
func (BankTsoVerifier) Verify(historyFile string) (bool, error) {
events, err := parseTsoEvents(historyFile)
// bankTsoChecker uses a direct way because we know every timestamp of the transaction.
// So we can order all transactions with timetamp and replay them.
type bankTsoChecker struct {
}

// Check checks the bank history.
func (bankTsoChecker) Check(_ core.Model, ops []core.Operation) (bool, error) {
events, err := pchecker.ConvertOperationsToEvents(ops)
if err != nil {
return false, err
}

return verifyTsoEvents(events), nil
tEvents := generateTsoEvents(events)
return verifyTsoEvents(tEvents), nil
}

// Name returns the name of the verifier.
func (BankTsoVerifier) Name() string {
return "bank_tso_verifier"
func (bankTsoChecker) Name() string {
return "tidb_bank_tso_checker"
}

// BankTsoChecker checks the bank history with the help of tso.
func BankTsoChecker() core.Checker {
return bankTsoChecker{}
}
Loading

0 comments on commit e7464e1

Please sign in to comment.