Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
165 changes: 165 additions & 0 deletions go/test/endtoend/docker/vttestserver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
/*
Copyright 2021 The Vitess Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package docker

import (
"encoding/json"
"fmt"
"os"
"os/exec"
"path"
"strconv"
"strings"
"time"

"vitess.io/vitess/go/vt/log"
)

const (
vttestserverMysql57image = "vttestserver-e2etest/mysql57"
vttestserverMysql80image = "vttestserver-e2etest/mysql80"
)

type vttestserver struct {
dockerImage string
keyspaces []string
numShards []int
mysqlMaxConnecetions int
port int
}

func newVttestserver(dockerImage string, keyspaces []string, numShards []int, mysqlMaxConnections, port int) *vttestserver {
return &vttestserver{
dockerImage: dockerImage,
keyspaces: keyspaces,
numShards: numShards,
mysqlMaxConnecetions: mysqlMaxConnections,
port: port,
}
}

func (v *vttestserver) teardown() {
cmd := exec.Command("docker", "rm", "--force", "vttestserver-end2end-test")
err := cmd.Run()
if err != nil {
log.Errorf("docker teardown failed :- %s", err.Error())
}
}

// startDockerImage starts the docker image for the vttestserver
func (v *vttestserver) startDockerImage() error {
cmd := exec.Command("docker", "run")
cmd.Args = append(cmd.Args, "--name=vttestserver-end2end-test")
cmd.Args = append(cmd.Args, "-p", fmt.Sprintf("%d:33577", v.port))
cmd.Args = append(cmd.Args, "-e", "PORT=33574")
cmd.Args = append(cmd.Args, "-e", fmt.Sprintf("KEYSPACES=%s", strings.Join(v.keyspaces, ",")))
cmd.Args = append(cmd.Args, "-e", fmt.Sprintf("NUM_SHARDS=%s", strings.Join(convertToStringSlice(v.numShards), ",")))
cmd.Args = append(cmd.Args, "-e", "MYSQL_BIND_HOST=0.0.0.0")
cmd.Args = append(cmd.Args, "-e", fmt.Sprintf("MYSQL_MAX_CONNECTIONS=%d", v.mysqlMaxConnecetions))
cmd.Args = append(cmd.Args, "--health-cmd", "mysqladmin ping -h127.0.0.1 -P33577")
cmd.Args = append(cmd.Args, "--health-interval=5s")
cmd.Args = append(cmd.Args, "--health-timeout=2s")
cmd.Args = append(cmd.Args, "--health-retries=5")
cmd.Args = append(cmd.Args, v.dockerImage)

err := cmd.Start()
if err != nil {
return err
}
return nil
}

// dockerStatus is a struct used to unmarshal json output from `docker inspect`
type dockerStatus struct {
State struct {
Health struct {
Status string
}
}
}

// waitUntilDockerHealthy waits until the docker image is healthy. It takes in as argument the amount of seconds to wait before timeout
func (v *vttestserver) waitUntilDockerHealthy(timeoutDelay int) error {
timeOut := time.After(time.Duration(timeoutDelay) * time.Second)

for {
select {
case <-timeOut:
// return error due to timeout
return fmt.Errorf("timed out waiting for docker image to start")
case <-time.After(time.Second):
cmd := exec.Command("docker", "inspect", "vttestserver-end2end-test")
out, err := cmd.Output()
if err != nil {
return err
}
var x []dockerStatus
err = json.Unmarshal(out, &x)
if err != nil {
return err
}
if len(x) > 0 {
status := x[0].State.Health.Status
if status == "healthy" {
return nil
}
}
}
}
}

// convertToStringSlice converts an integer slice to string slice
func convertToStringSlice(intSlice []int) []string {
var stringSlice []string
for _, val := range intSlice {
str := strconv.Itoa(val)
stringSlice = append(stringSlice, str)
}
return stringSlice
}

//makeVttestserverDockerImages creates the vttestserver docker images for both MySQL57 and MySQL80
func makeVttestserverDockerImages() error {
mainVitessPath := path.Join(os.Getenv("PWD"), "../../../..")
dockerFilePath := path.Join(mainVitessPath, "docker/vttestserver/Dockerfile.mysql57")
cmd57 := exec.Command("docker", "build", "-f", dockerFilePath, "-t", vttestserverMysql57image, ".")
cmd57.Dir = mainVitessPath
err := cmd57.Start()
if err != nil {
return err
}

dockerFilePath = path.Join(mainVitessPath, "docker/vttestserver/Dockerfile.mysql80")
cmd80 := exec.Command("docker", "build", "-f", dockerFilePath, "-t", vttestserverMysql80image, ".")
cmd80.Dir = mainVitessPath
err = cmd80.Start()
if err != nil {
return err
}

err = cmd57.Wait()
if err != nil {
return err
}

err = cmd80.Wait()
if err != nil {
return err
}

return nil
}
196 changes: 196 additions & 0 deletions go/test/endtoend/docker/vttestserver_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
/*
Copyright 2021 The Vitess Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package docker

import (
"context"
"fmt"
"os"
"testing"

"github.com/google/go-cmp/cmp"

"vitess.io/vitess/go/sqltypes"

"vitess.io/vitess/go/mysql"

"github.com/stretchr/testify/require"
)

func TestMain(m *testing.M) {
exitCode := func() int {
err := makeVttestserverDockerImages()
if err != nil {
return 1
}
return m.Run()
}()
os.Exit(exitCode)
}

func TestUnsharded(t *testing.T) {
dockerImages := []string{vttestserverMysql57image, vttestserverMysql80image}
for _, image := range dockerImages {
t.Run(image, func(t *testing.T) {
vtest := newVttestserver(image, []string{"unsharded_ks"}, []int{1}, 1000, 33577)
err := vtest.startDockerImage()
require.NoError(t, err)
defer vtest.teardown()

// wait for the docker to be setup
err = vtest.waitUntilDockerHealthy(10)
require.NoError(t, err)

ctx := context.Background()
vttestParams := mysql.ConnParams{
Host: "localhost",
Port: vtest.port,
}
conn, err := mysql.Connect(ctx, &vttestParams)
require.NoError(t, err)
defer conn.Close()
assertMatches(t, conn, "show databases", `[[VARCHAR("unsharded_ks")] [VARCHAR("information_schema")] [VARCHAR("mysql")] [VARCHAR("sys")] [VARCHAR("performance_schema")]]`)
_, err = execute(t, conn, "create table unsharded_ks.t1(id int)")
require.NoError(t, err)
_, err = execute(t, conn, "insert into unsharded_ks.t1(id) values (10),(20),(30)")
require.NoError(t, err)
assertMatches(t, conn, "select * from unsharded_ks.t1", `[[INT32(10)] [INT32(20)] [INT32(30)]]`)
})
}
}

func TestSharded(t *testing.T) {
dockerImages := []string{vttestserverMysql57image, vttestserverMysql80image}
for _, image := range dockerImages {
t.Run(image, func(t *testing.T) {
vtest := newVttestserver(image, []string{"ks"}, []int{2}, 1000, 33577)
err := vtest.startDockerImage()
require.NoError(t, err)
defer vtest.teardown()

// wait for the docker to be setup
err = vtest.waitUntilDockerHealthy(10)
require.NoError(t, err)

ctx := context.Background()
vttestParams := mysql.ConnParams{
Host: "localhost",
Port: vtest.port,
}
conn, err := mysql.Connect(ctx, &vttestParams)
require.NoError(t, err)
defer conn.Close()
assertMatches(t, conn, "show databases", `[[VARCHAR("ks")] [VARCHAR("information_schema")] [VARCHAR("mysql")] [VARCHAR("sys")] [VARCHAR("performance_schema")]]`)
_, err = execute(t, conn, "create table ks.t1(id int)")
require.NoError(t, err)
_, err = execute(t, conn, "alter vschema on ks.t1 add vindex `binary_md5`(id) using `binary_md5`")
require.NoError(t, err)
_, err = execute(t, conn, "insert into ks.t1(id) values (10),(20),(30)")
require.NoError(t, err)
assertMatches(t, conn, "select id from ks.t1 order by id", `[[INT32(10)] [INT32(20)] [INT32(30)]]`)
})
}
}

func TestMysqlMaxCons(t *testing.T) {
dockerImages := []string{vttestserverMysql57image, vttestserverMysql80image}
for _, image := range dockerImages {
t.Run(image, func(t *testing.T) {
vtest := newVttestserver(image, []string{"ks"}, []int{2}, 100000, 33577)
err := vtest.startDockerImage()
require.NoError(t, err)
defer vtest.teardown()

// wait for the docker to be setup
err = vtest.waitUntilDockerHealthy(10)
require.NoError(t, err)

ctx := context.Background()
vttestParams := mysql.ConnParams{
Host: "localhost",
Port: vtest.port,
}
conn, err := mysql.Connect(ctx, &vttestParams)
require.NoError(t, err)
defer conn.Close()
assertMatches(t, conn, "select @@max_connections", `[[UINT64(100000)]]`)
})
}
}

func TestLargeNumberOfKeyspaces(t *testing.T) {
dockerImages := []string{vttestserverMysql57image, vttestserverMysql80image}
for _, image := range dockerImages {
t.Run(image, func(t *testing.T) {
var keyspaces []string
var numShards []int
for i := 0; i < 100; i++ {
keyspaces = append(keyspaces, fmt.Sprintf("unsharded_ks%d", i))
numShards = append(numShards, 1)
}

vtest := newVttestserver(image, keyspaces, numShards, 100000, 33577)
err := vtest.startDockerImage()
require.NoError(t, err)
defer vtest.teardown()

// wait for the docker to be setup
err = vtest.waitUntilDockerHealthy(15)
require.NoError(t, err)

ctx := context.Background()
vttestParams := mysql.ConnParams{
Host: "localhost",
Port: vtest.port,
}
conn, err := mysql.Connect(ctx, &vttestParams)
require.NoError(t, err)
defer conn.Close()

// assert that all the keyspaces are correctly setup
for _, keyspace := range keyspaces {
_, err = execute(t, conn, "create table "+keyspace+".t1(id int)")
require.NoError(t, err)
_, err = execute(t, conn, "insert into "+keyspace+".t1(id) values (10),(20),(30)")
require.NoError(t, err)
assertMatches(t, conn, "select * from "+keyspace+".t1", `[[INT32(10)] [INT32(20)] [INT32(30)]]`)
}
})
}
}

func execute(t *testing.T, conn *mysql.Conn, query string) (*sqltypes.Result, error) {
t.Helper()
return conn.ExecuteFetch(query, 1000, true)
}

func checkedExec(t *testing.T, conn *mysql.Conn, query string) *sqltypes.Result {
t.Helper()
qr, err := conn.ExecuteFetch(query, 1000, true)
require.NoError(t, err)
return qr
}

func assertMatches(t *testing.T, conn *mysql.Conn, query, expected string) {
t.Helper()
qr := checkedExec(t, conn, query)
got := fmt.Sprintf("%v", qr.Rows)
diff := cmp.Diff(expected, got)
if diff != "" {
t.Errorf("Query: %s (-want +got):\n%s", query, diff)
}
}