Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create db #7246

Closed
wants to merge 2 commits into from
Closed
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
113 changes: 113 additions & 0 deletions api_approaches
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
I see two ways to implement the "hook" for this:

1) ExecuteCreateKeyspace calls some async endpoint that was passed in a CLI flag to vtgate:

ExecuteCreateKeyspace:
Does keyspace exist in topo?
true:
Return a mysql error that database already exists.
false:
vtgate sends request to keyspace create service
other added keyspace:
Nothing happens at this step.
keyspace service partition:
Retry loop to send request.
vtgate partition:
(1) The user is disconnected. When they reconnect, if they run `CREATE DATABASE` again, they'll end up sending another request if the first hasn't finished.
Depending on what the other side does, that's probably fine.
vtgate and keyspace service partition:
The user is disconnected and the request is lost.
true:
(2) The request has succesfully been submitted. Does keyspace exists in topo?
true:
Return success to the user.
false:
Repeat (2).
vtgate partition:
Same as (1).
vtgate partition:
The CREATE DATABASE request is lost and the user is disconnected.
keyspace removed:
Returns a mysql error that database already exists, even though it does not.



2) ExecuteCreateKeyspace records the "DesiredKeyspace" in the topo server in a dir seperate from keyspace, and anything who wants to repond to desired keyspace requests can subscribe to it in some way. psuedocode

ExecuteCreateKeyspace:
Does keyspace exist in topo?
true:
Does desired keyspace exist in topo?
true:
Clean up orphaned desired keyspace and return mysql error that database already exists.
false:
Return mysql error that database already exists.
vtgate partition:
The CREATE DATABASE request is lost and the user is disconnected.
keyspace removed:
Any orphan desired state is correctly cleaned up.We return that the database exists even though it does not.
false:
Try to create desired keyspace in topo.
vtgate partition:
The CREATE DATABASE request is saved and the user is disconnected. If they attempt to rerun the CREATE, they will end up at (1) or (2).
Did the desired keyspace already exist in topo?
true:
Does the desired keyspace in topo match our desired keyspace? (This will always be true when the only piece of information we're saving is the keyspace name)
true:
(1) Block and poll keyspace until it exists.
Remove desired keyspace.
Return success to the user.
false:
(2) Return error to the user that a different keyspace request is already in progress with params : %v.
false:
Block and poll keyspace until it exists.
Remove desired keyspace.
Return success to the user.
false:

vtgate partition:
The CREATE DATABASE request is lost and the user is disconnected.




update topserver DesiredKeyspace
already exists: return an error that states keyspace creation already in progress [or block]
does not exit:



does Keyspace exit? Remove DesiredKeyspace and return
if not, poll getKeyspaces until it pops up, then remove desiredkeyspace and return

in thirdpartyservice [presumably you'd only have one consumer, the operator]:








When it's fullfilled the desire, it removes it from the topo server, and CREATE DATABSE returns.



Advantages of approach 1:
1) YAGNI - it's relatively straightfoward to implement.
2) We don't need asynchoncitiy in this case, we're going to block the CREATE DATABASE until the creation is complete.

Disadvantages of approach 1:
1) It's quasi racey. However, that doesn't matter for CREATE DABASE <keyspace> specifically because
2) I find it much harder to reason about the non-happy path in terms of network issues.
3) If we ever introduce anything like "CREATE DATABASE hello SHARDS 3", we have a race.
4) DNS or whatever to "catch the tail" of the operator




disavdnatnges of approach 2:
dependiy might not be quite as clear

advanatages of approach 2
easier operationally
2 changes: 2 additions & 0 deletions examples/local/scripts/vtgate-up.sh
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ vtgate \
-service_map 'grpc-vtgateservice' \
-pid_file $VTDATAROOT/tmp/vtgate.pid \
-mysql_auth_server_impl none \
-provision_authorized_users '%' \
-provisioner_type 'grpc' \
> $VTDATAROOT/tmp/vtgate.out 2>&1 &

# Block waiting for vtgate to be listening
Expand Down
2 changes: 1 addition & 1 deletion examples/local/scripts/vttablet-down.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ source ./env.sh
printf -v tablet_dir 'vt_%010d' $TABLET_UID
pid=`cat $VTDATAROOT/$tablet_dir/vttablet.pid`

kill $pid
kill -9 $pid

# Wait for vttablet to die.
while ps -p $pid > /dev/null; do sleep 1; done
Expand Down
17 changes: 17 additions & 0 deletions go/test/endtoend/cluster/vtctl_process.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,23 @@ func (vtctl *VtctlProcess) CreateKeyspace(keyspace string) (err error) {
return tmpProcess.Run()
}

// DeleteKeyspace executes vtctl command to create keyspace
func (vtctl *VtctlProcess) DeleteKeyspace(keyspace string) (err error) {
tmpProcess := exec.Command(
vtctl.Binary,
"-topo_implementation", vtctl.TopoImplementation,
"-topo_global_server_address", vtctl.TopoGlobalAddress,
"-topo_global_root", vtctl.TopoGlobalRoot,
)
if *isCoverage {
tmpProcess.Args = append(tmpProcess.Args, "-test.coverprofile="+getCoveragePath("vtctl-delete-ks.out"))
}
tmpProcess.Args = append(tmpProcess.Args,
"DeleteKeyspace", keyspace)
log.Infof("Running DeleteKeyspace with command: %v", strings.Join(tmpProcess.Args, " "))
return tmpProcess.Run()
}

// ExecuteCommandWithOutput executes any vtctlclient command and returns output
func (vtctl *VtctlProcess) ExecuteCommandWithOutput(args ...string) (result string, err error) {
args = append([]string{
Expand Down
167 changes: 167 additions & 0 deletions go/test/endtoend/provision/plugin_grpc_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
/*
Copyright 2019 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 sequence

import (
"context"
"flag"
"fmt"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
"net"
"os"
"testing"
"time"
"vitess.io/vitess/go/mysql"
"vitess.io/vitess/go/test/endtoend/cluster"
"vitess.io/vitess/go/vt/log"
"vitess.io/vitess/go/vt/proto/provision"
)

var (
clusterForProvisionTest *cluster.LocalProcessCluster
cell = "zone1"
hostname = "localhost"
keyspace = "keyspace"
)

func TestMain(m *testing.M) {
defer cluster.PanicHandler(nil)
flag.Parse()

exitCode := func() int {

var lc net.ListenConfig
listener, err := lc.Listen(context.Background(), "tcp", "localhost:")
if err != nil {
log.Error(err)
return 1
}

defer listener.Close()

go func() {
log.Error(startProvisionerServer(listener))
}()

clusterForProvisionTest = cluster.NewCluster(cell, hostname)
//FIXME: underscores or dashes
clusterForProvisionTest.VtGateExtraArgs = []string {
"-provision_create_keyspace_authorized_users",
"%",
"-provision_delete_keyspace_authorized_users",
"%",
"-provision_timeout",
"30s",
"-provision_type",
"grpc",
"-provision_grpc_endpoint",
listener.Addr().String(),
"-provision_grpc_dial_timeout",
"1s",
"-provision_grpc_per_retry_timeout",
"1s",
"-provision_grpc_max_retries",
"1",
}

defer clusterForProvisionTest.Teardown()

if err := clusterForProvisionTest.StartTopo(); err != nil {
return 1
}

if err := clusterForProvisionTest.StartVtgate(); err != nil {
return 1
}

return m.Run()
}()
os.Exit(exitCode)
}

func TestProvisionKeyspace(t *testing.T) {
defer cluster.PanicHandler(t)

ctx := context.Background()
vtParams := mysql.ConnParams{
Host: clusterForProvisionTest.Hostname,
Port: clusterForProvisionTest.VtgateMySQLPort,
ConnectTimeoutMs: 1000,
}
conn, err := mysql.Connect(ctx, &vtParams)
require.Nil(t, err)

createStatement := fmt.Sprintf("CREATE DATABASE %s;", keyspace)
qr, err := conn.ExecuteFetch(createStatement, 10, true)
require.Nil(t, err)

assert.Equal(t, uint64(1), qr.RowsAffected, "returned: %v", qr.Rows)

_, err = clusterForProvisionTest.VtctlclientProcess.ExecuteCommandWithOutput("GetKeyspace", keyspace)
//If GetKeyspace doesn't return an error, the keyspace exists.
require.Nil(t, err)

dropStatement := fmt.Sprintf("DROP DATABASE %s;", keyspace)
qr, err = conn.ExecuteFetch(dropStatement, 10, true)
require.Nil(t, err)

assert.Equal(t, uint64(1), qr.RowsAffected, "returned: %v", qr.Rows)

_, err = clusterForProvisionTest.VtctlclientProcess.ExecuteCommandWithOutput("GetKeyspace", keyspace)
//If GetKeyspace does return an error, we assume it's because the keyspace no longer exists, and not because of
//a network error.
assert.True(t, err != nil, "keyspace %s was not deleted", keyspace)
}

type testGrpcServer struct {}

func (_ testGrpcServer)RequestCreateKeyspace(ctx context.Context, rckr *provision.RequestCreateKeyspaceRequest) (*provision.ProvisionResponse, error) {
//We're doing this in a go routine to simulate the fact that RequestCreateKeyspace does not block while the
//the keyspace is being created. We want to exercise the topo polling logic, so we use a large wait time.
go func() {
<- time.After(10 * time.Second)
err := clusterForProvisionTest.VtctlProcess.CreateKeyspace(rckr.Keyspace)
if err != nil {
log.Error(err)
}
}()
return &provision.ProvisionResponse{}, nil
}

func (_ testGrpcServer)RequestDeleteKeyspace(ctx context.Context, rckr *provision.RequestDeleteKeyspaceRequest) (*provision.ProvisionResponse, error) {
//We're doing this in a go routine to simulate the fact that RequestDeleteKeyspace does not block while the
//the keyspace is being deleted. We want to exercise the topo polling logic, so we use a large wait time.
go func() {
<- time.After(10 * time.Second)
err := clusterForProvisionTest.VtctlProcess.DeleteKeyspace(rckr.Keyspace)
if err != nil {
log.Error(err)
}
}()
return &provision.ProvisionResponse{}, nil
}

func startProvisionerServer(listener net.Listener) error {
var opts []grpc.ServerOption
grpcServer := grpc.NewServer(opts...)
defer grpcServer.Stop()

provision.RegisterProvisionServer(grpcServer, testGrpcServer{})
return grpcServer.Serve(listener)
}
Loading