diff --git a/charts/const.go b/charts/const.go new file mode 100644 index 00000000..fd8c4f32 --- /dev/null +++ b/charts/const.go @@ -0,0 +1,30 @@ +// Copyright 2017 AMIS Technologies +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package charts + +import ( + "os" + "path/filepath" +) + +var ( + chartBasePath string +) + +func init() { + chartBasePath = filepath.Join(os.Getenv("CURDIR"), "benchmark/kubernetes") +} diff --git a/charts/genesis.go b/charts/genesis.go new file mode 100644 index 00000000..2abb5e5d --- /dev/null +++ b/charts/genesis.go @@ -0,0 +1,80 @@ +// Copyright 2017 AMIS Technologies +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package charts + +import ( + "fmt" + "log" + "os" + "path/filepath" + "strings" + + "github.com/ethereum/go-ethereum/common" + + "github.com/getamis/istanbul-tools/genesis" +) + +type GenesisChart struct { + name string + chartPath string + genesisFile string + args []string +} + +func NewGenesisChart(addrs []common.Address, gasLimit uint64) *GenesisChart { + chartPath := filepath.Join(chartBasePath, "genesis-block") + genesisPath := filepath.Join(chartPath, ".genesis") + err := os.MkdirAll(genesisPath, 0700) + if err != nil { + log.Fatal(err) + } + + chart := &GenesisChart{ + name: "genesis-block", + chartPath: chartPath, + genesisFile: genesis.NewFileAt( + genesisPath, + false, + genesis.Validators(addrs...), + genesis.GasLimit(gasLimit), + ), + } + + relPath := strings.Replace(chart.genesisFile, chartPath+"/", "", 1) + chart.Override("genesisFileName", relPath) + + return chart +} + +func (chart *GenesisChart) Override(key, value string) { + chart.args = append(chart.args, fmt.Sprintf("%s=%s", key, value)) +} + +func (chart *GenesisChart) Install(debug bool) error { + defer os.RemoveAll(filepath.Dir(chart.genesisFile)) + + return installRelease( + chart.name, + chart.args, + chart.chartPath, + debug, + ) +} + +func (chart *GenesisChart) Uninstall() error { + return uninstallRelease(chart.name) +} diff --git a/charts/static_nodes.go b/charts/static_nodes.go new file mode 100644 index 00000000..fcd4c09a --- /dev/null +++ b/charts/static_nodes.go @@ -0,0 +1,78 @@ +// Copyright 2017 AMIS Technologies +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package charts + +import ( + "fmt" + "log" + "os" + "path/filepath" + "strings" + + "github.com/getamis/istanbul-tools/common" +) + +type StaticNodesChart struct { + name string + chartPath string + staticNodesFile string + args []string +} + +func NewStaticNodesChart(nodekeys []string, ipAddrs []string) *StaticNodesChart { + chartPath := filepath.Join(chartBasePath, "static-nodes") + staticNodesPath := filepath.Join(chartPath, ".static-nodes") + err := os.MkdirAll(staticNodesPath, 0700) + if err != nil { + log.Fatal(err) + } + + if len(nodekeys) != len(ipAddrs) { + log.Println("The number of nodekeys and the number of IP address should be equal") + return nil + } + + chart := &StaticNodesChart{ + name: "static-nodes", + chartPath: chartPath, + staticNodesFile: common.GenerateStaticNodesAt(staticNodesPath, nodekeys, ipAddrs), + } + + relPath := strings.Replace(chart.staticNodesFile, chartPath+"/", "", 1) + chart.Override("fileName", relPath) + + return chart +} + +func (chart *StaticNodesChart) Override(key, value string) { + chart.args = append(chart.args, fmt.Sprintf("%s=%s", key, value)) +} + +func (chart *StaticNodesChart) Install(debug bool) error { + defer os.RemoveAll(filepath.Dir(chart.staticNodesFile)) + + return installRelease( + chart.name, + chart.args, + chart.chartPath, + debug, + ) +} + +func (chart *StaticNodesChart) Uninstall() error { + return uninstallRelease(chart.name) +} diff --git a/charts/utils.go b/charts/utils.go new file mode 100644 index 00000000..3ca59063 --- /dev/null +++ b/charts/utils.go @@ -0,0 +1,98 @@ +// Copyright 2017 AMIS Technologies +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package charts + +import ( + "errors" + "fmt" + "os/exec" + "strings" +) + +func newInstallCommand() *exec.Cmd { + return exec.Command("helm", "install") +} + +func newUninstallCommand() *exec.Cmd { + return exec.Command("helm", "delete", "--purge") +} + +func installRelease(name string, args []string, path string, debug bool) error { + cmd := newInstallCommand() + + if name != "" { + cmd.Args = append(cmd.Args, "--name") + cmd.Args = append(cmd.Args, name) + } + + if len(args) > 0 { + cmd.Args = append(cmd.Args, "--set") + cmd.Args = append(cmd.Args, strings.Join(args, ",")) + } + + cmd.Args = append(cmd.Args, path) + + if debug { + cmd.Args = append(cmd.Args, "--dry-run") + cmd.Args = append(cmd.Args, "--debug") + } + + cmd.Args = append(cmd.Args, "--wait") + + if debug { + fmt.Println(cmd.Args) + } + + output, err := cmd.CombinedOutput() + if err != nil { + fmt.Println(err, string(output)) + return err + } + + fmt.Println(string(output)) + return nil +} + +func uninstallRelease(release string) error { + cmd := newUninstallCommand() + + if release != "" { + cmd.Args = append(cmd.Args, release) + } else { + return errors.New("Unknown release name") + } + + output, err := cmd.CombinedOutput() + if err != nil { + fmt.Println(err) + return err + } + + fmt.Println(string(output)) + return nil +} + +func ListCharts() { + cmd := exec.Command("helm", "list") + output, err := cmd.Output() + if err != nil { + fmt.Println(err) + return + } + + fmt.Println(string(output)) +} diff --git a/charts/validator.go b/charts/validator.go new file mode 100644 index 00000000..974a3ae0 --- /dev/null +++ b/charts/validator.go @@ -0,0 +1,61 @@ +// Copyright 2017 AMIS Technologies +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package charts + +import ( + "fmt" + "path/filepath" +) + +type ValidatorChart struct { + name string + chartPath string + args []string +} + +func NewValidatorChart(name string, args []string) *ValidatorChart { + chartPath := filepath.Join(chartBasePath, "validator") + + chart := &ValidatorChart{ + name: "validator-" + name, + args: args, + chartPath: chartPath, + } + + return chart +} + +func (chart *ValidatorChart) Override(key, value string) { + chart.args = append(chart.args, fmt.Sprintf("%s=%s", key, value)) +} + +func (chart *ValidatorChart) Install(debug bool) error { + return installRelease( + chart.name, + chart.args, + chart.chartPath, + debug, + ) +} + +func (chart *ValidatorChart) Uninstall() error { + return uninstallRelease(chart.name) +} + +func (chart *ValidatorChart) Name() string { + return chart.name +} diff --git a/common/utils.go b/common/utils.go index 2f5c200d..6a31a8fb 100644 --- a/common/utils.go +++ b/common/utils.go @@ -19,13 +19,17 @@ package common import ( "crypto/ecdsa" "crypto/rand" + "encoding/json" "fmt" + "io/ioutil" "log" + "net" "os" "path/filepath" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" + "github.com/getamis/go-ethereum/p2p/discover" uuid "github.com/satori/go.uuid" ) @@ -35,6 +39,14 @@ const ( nodekeyFileName = "nodekey" ) +func GenerateIPs(num int) (ips []string) { + for i := 0; i < num; i++ { + ips = append(ips, fmt.Sprintf("10.1.1.%d", i+2)) + } + + return ips +} + func GenerateRandomDir() (string, error) { err := os.MkdirAll(filepath.Join(defaultLocalDir), 0700) if err != nil { @@ -83,6 +95,44 @@ func SaveNodeKey(key *ecdsa.PrivateKey, dataDir string) error { return nil } +func GenerateStaticNodesAt(dir string, nodekeys []string, ipAddrs []string) (filename string) { + var nodes []string + + for i, nodekey := range nodekeys { + key, err := crypto.HexToECDSA(nodekey) + if err != nil { + log.Printf("Failed to create key, err: %v\n", err) + return "" + } + node := discover.NewNode( + discover.PubkeyID(&key.PublicKey), + net.ParseIP(ipAddrs[i]), + 0, + uint16(30303)) + + nodes = append(nodes, node.String()) + } + + filename = filepath.Join(dir, "static-nodes.json") + bytes, _ := json.Marshal(nodes) + if err := ioutil.WriteFile(filename, bytes, 0644); err != nil { + log.Printf("Failed to write '%s', err: %v\n", filename, err) + return "" + } + + return filename +} + +func GenerateStaticNodes(nodekeys []string, ipAddrs []string) (filename string) { + dir, err := GenerateRandomDir() + if err != nil { + log.Printf("Failed to generate directory, err: %v\n", err) + return "" + } + + return GenerateStaticNodesAt(dir, nodekeys, ipAddrs) +} + func RandomHex() string { b, _ := RandomBytes(32) return common.BytesToHash(b).Hex() diff --git a/tests/chart_installer.go b/tests/chart_installer.go new file mode 100644 index 00000000..7fc222a6 --- /dev/null +++ b/tests/chart_installer.go @@ -0,0 +1,22 @@ +// Copyright 2017 AMIS Technologies +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package tests + +type ChartInstaller interface { + Install(bool) error + Uninstall() error +} diff --git a/tests/load/load_test.go b/tests/load/load_test.go new file mode 100644 index 00000000..4399e629 --- /dev/null +++ b/tests/load/load_test.go @@ -0,0 +1,85 @@ +// Copyright 2017 AMIS Technologies +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package load + +import ( + "testing" + + "github.com/getamis/istanbul-tools/charts" + "github.com/getamis/istanbul-tools/common" + + "github.com/getamis/istanbul-tools/tests" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("TPS-01: Large amount of transactions", func() { + tests.CaseTable("with number of validators", + func(numberOfValidators int) { + tests.CaseTable("with gas limit", + func(gaslimit int) { + tests.CaseTable("with txpool size", + func(txpoolSize int) { + runTests(numberOfValidators, gaslimit, txpoolSize) + }, + + tests.Case("2048", 2048), + tests.Case("10240", 10240), + ) + }, + + tests.Case("21000*1000", 21000*1000), + tests.Case("21000*3000", 21000*3000), + ) + }, + + tests.Case("4 validators", 4), + tests.Case("7 validators", 7), + tests.Case("10 validators", 10), + ) +}) + +func runTests(numberOfValidators int, gaslimit int, txpoolSize int) { + Describe("", func() { + var ( + genesisChart tests.ChartInstaller + staticNodesChart tests.ChartInstaller + ) + + BeforeEach(func() { + _, nodekeys, addrs := common.GenerateKeys(numberOfValidators) + genesisChart = charts.NewGenesisChart(addrs, uint64(gaslimit)) + Expect(genesisChart.Install(false)).To(BeNil()) + + staticNodesChart = charts.NewStaticNodesChart(nodekeys, common.GenerateIPs(len(nodekeys))) + Expect(staticNodesChart.Install(false)).To(BeNil()) + }) + + AfterEach(func() { + Expect(genesisChart.Uninstall()).To(BeNil()) + Expect(staticNodesChart.Uninstall()).To(BeNil()) + }) + + It("", func() { + }) + }) +} + +func IstanbulLoadTest(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Istanbul Load Test Suite") +} diff --git a/tests/table.go b/tests/table.go new file mode 100644 index 00000000..f1802a17 --- /dev/null +++ b/tests/table.go @@ -0,0 +1,80 @@ +// Copyright 2017 AMIS Technologies +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package tests + +import ( + "fmt" + "reflect" + + "github.com/onsi/ginkgo" +) + +func CaseTable(description string, itBody interface{}, entries ...tableEntry) bool { + caseTable(description, itBody, entries, false, false) + return true +} + +/* +You can focus a table with `FCaseTable`. +*/ +func FCaseTable(description string, itBody interface{}, entries ...tableEntry) bool { + caseTable(description, itBody, entries, false, true) + return true +} + +/* +You can mark a table as pending with `PCaseTable`. +*/ +func PCaseTable(description string, itBody interface{}, entries ...tableEntry) bool { + caseTable(description, itBody, entries, true, false) + return true +} + +/* +You can mark a table as pending with `XCaseTable`. +*/ +func XCaseTable(description string, itBody interface{}, entries ...tableEntry) bool { + caseTable(description, itBody, entries, true, false) + return true +} + +func caseTable(description string, itBody interface{}, entries []tableEntry, pending bool, focused bool) { + itBodyValue := reflect.ValueOf(itBody) + if itBodyValue.Kind() != reflect.Func { + panic(fmt.Sprintf("DescribeTable expects a function, got %#v", itBody)) + } + + if pending { + ginkgo.PDescribe(description, func() { + for _, entry := range entries { + entry.generate(itBodyValue, entries, pending, focused) + } + }) + } else if focused { + ginkgo.FDescribe(description, func() { + for _, entry := range entries { + entry.generate(itBodyValue, entries, pending, focused) + } + }) + } else { + ginkgo.Describe(description, func() { + for _, entry := range entries { + entry.generate(itBodyValue, entries, pending, focused) + } + }) + } +} diff --git a/tests/table_entry.go b/tests/table_entry.go new file mode 100644 index 00000000..09e9c070 --- /dev/null +++ b/tests/table_entry.go @@ -0,0 +1,85 @@ +package tests + +import ( + "reflect" + + "github.com/onsi/ginkgo" +) + +/* +TableEntry represents an entry in a table test. You generally use the `Entry` constructor. +*/ +type tableEntry struct { + Description string + Parameters []interface{} + Pending bool + Focused bool +} + +func (t tableEntry) generate(itBody reflect.Value, entries []tableEntry, pending bool, focused bool) { + if t.Pending { + ginkgo.PDescribe(t.Description, func() { + for _, entry := range entries { + entry.generate(itBody, entries, pending, focused) + } + }) + return + } + + values := []reflect.Value{} + for i, param := range t.Parameters { + var value reflect.Value + + if param == nil { + inType := itBody.Type().In(i) + value = reflect.Zero(inType) + } else { + value = reflect.ValueOf(param) + } + + values = append(values, value) + } + + body := func() { + itBody.Call(values) + } + + if t.Focused { + ginkgo.FDescribe(t.Description, body) + } else { + ginkgo.Describe(t.Description, body) + } +} + +/* +Entry constructs a tableEntry. + +The first argument is a required description (this becomes the content of the generated Ginkgo `It`). +Subsequent parameters are saved off and sent to the callback passed in to `DescribeTable`. + +Each Entry ends up generating an individual Ginkgo It. +*/ +func Case(description string, parameters ...interface{}) tableEntry { + return tableEntry{description, parameters, false, false} +} + +/* +You can focus a particular entry with FEntry. This is equivalent to FIt. +*/ +func FCase(description string, parameters ...interface{}) tableEntry { + return tableEntry{description, parameters, false, true} +} + +/* +You can mark a particular entry as pending with PEntry. This is equivalent to PIt. +*/ +func PCase(description string, parameters ...interface{}) tableEntry { + return tableEntry{description, parameters, true, false} +} + +/* +You can mark a particular entry as pending with XEntry. This is equivalent to XIt. +*/ +func XCase(description string, parameters ...interface{}) tableEntry { + return tableEntry{description, parameters, true, false} +}