Skip to content

Commit

Permalink
replicaset: add bootstrap command
Browse files Browse the repository at this point in the history
Part of #316
  • Loading branch information
askalt authored and psergee committed Jun 25, 2024
1 parent 99d3eac commit 8ecb32a
Show file tree
Hide file tree
Showing 4 changed files with 265 additions and 9 deletions.
76 changes: 67 additions & 9 deletions cli/cmd/replicaset.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,18 @@ var (
replicaset.OrchestratorCustom: &orchestratorCustom,
}

replicasetUser string
replicasetPassword string
replicasetSslKeyFile string
replicasetSslCertFile string
replicasetSslCaFile string
replicasetSslCiphers string
replicasetForce bool
replicasetTimeout int
replicasetIntegrityPrivateKey string
replicasetUser string
replicasetPassword string
replicasetSslKeyFile string
replicasetSslCertFile string
replicasetSslCaFile string
replicasetSslCiphers string
replicasetForce bool
replicasetTimeout int
replicasetIntegrityPrivateKey string
replicasetBootstrapVshard bool
replicasetCartridgeReplicasetsFile string
replicasetReplicasetName string

replicasetUriHelp = " The URI can be specified in the following formats:\n" +
" * [tcp://][username:password@][host:port]\n" +
Expand Down Expand Up @@ -149,6 +152,34 @@ func newExpelCmd() *cobra.Command {
return cmd
}

// newBootstrapCmd creates a "replicaset bootstrap" command.
func newBootstrapCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "bootstrap [--timeout secs] [flags] <APP_NAME|APP_NAME:INSTANCE_NAME>",
Short: "Bootstrap an application or instance",
Long: "Bootstrap an application or instance.",
Run: func(cmd *cobra.Command, args []string) {
cmdCtx.CommandName = cmd.Name()
err := modules.RunCmd(&cmdCtx, cmd.CommandPath(), &modulesInfo,
internalReplicasetBootstrapModule, args)
util.HandleCmdErr(cmd, err)
},
Args: cobra.ExactArgs(1),
}

addOrchestratorFlags(cmd)
cmd.Flags().BoolVarP(&replicasetBootstrapVshard, "bootstrap-vshard", "", false,
"bootstrap vshard")
cmd.Flags().StringVarP(&replicasetCartridgeReplicasetsFile, "file", "", "",
`file where replicasets configuration is described (default "<APP_DIR>/instances.yml")`)
cmd.Flags().StringVarP(&replicasetReplicasetName, "replicaset", "",
"", "replicaset name for an instance bootstrapping")
cmd.Flags().IntVarP(&replicasetTimeout, "timeout", "", replicasetcmd.
VShardBootstrapDefaultTimeout, "timeout")

return cmd
}

// newBootstrapVShardCmd creates a "vshard bootstrap" command.
func newBootstrapVShardCmd() *cobra.Command {
cmd := &cobra.Command{
Expand Down Expand Up @@ -202,6 +233,7 @@ func NewReplicasetCmd() *cobra.Command {
cmd.AddCommand(newDemoteCmd())
cmd.AddCommand(newExpelCmd())
cmd.AddCommand(newVShardCmd())
cmd.AddCommand(newBootstrapCmd())

return cmd
}
Expand Down Expand Up @@ -452,6 +484,32 @@ func internalReplicasetBootstrapVShardModule(cmdCtx *cmdcontext.CmdCtx, args []s
})
}

// internalReplicasetBootstrapModule is a "bootstrap" command for the "replicaset" module.
func internalReplicasetBootstrapModule(cmdCtx *cmdcontext.CmdCtx, args []string) error {
_, instName, found := strings.Cut(args[0], string(running.InstanceDelimiter))

var ctx replicasetCtx
if err := replicasetFillCtx(cmdCtx, &ctx, args, true); err != nil {
return err
}
if ctx.IsInstanceConnect {
defer ctx.Conn.Close()
}
bootstrapCtx := replicasetcmd.BootstapCtx{
ReplicasetsFile: replicasetCartridgeReplicasetsFile,
Orchestrator: ctx.Orchestrator,
RunningCtx: ctx.RunningCtx,
Timeout: replicasetTimeout,
BootstrapVShard: replicasetBootstrapVshard,
Replicaset: replicasetReplicasetName,
}
if found {
bootstrapCtx.Instance = instName
}

return replicasetcmd.Bootstrap(bootstrapCtx)
}

// getOrchestartor returns a chosen orchestrator or an unknown one.
func getOrchestrator() (replicaset.Orchestrator, error) {
orchestrator := replicaset.OrchestratorUnknown
Expand Down
73 changes: 73 additions & 0 deletions cli/replicaset/cmd/bootstrap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package replicasetcmd

import (
"fmt"

"github.com/apex/log"
"github.com/tarantool/tt/cli/replicaset"
"github.com/tarantool/tt/cli/running"
)

// BootstrapCtx describes context to bootstrap an instance or application.
type BootstapCtx struct {
// ReplicasetsFile is a Cartridge replicasets file.
ReplicasetsFile string
// Orchestrator is a forced orchestator choice.
Orchestrator replicaset.Orchestrator
// RunningCtx is an application running context.
RunningCtx running.RunningCtx
// Timeout describes a timeout in seconds.
// We keep int as it can be passed to the target instance.
Timeout int
// BootstrapVShard is true when the vshard must be bootstrapped.
BootstrapVShard bool
// Instance is an instance name to bootstrap.
Instance string
// Replicaset is a replicaset name for an instance bootstrapping.
Replicaset string
}

// Bootstrap bootstraps an instance or application.
func Bootstrap(ctx BootstapCtx) error {
if ctx.Instance != "" {
if ctx.Replicaset == "" {
return fmt.Errorf("the replicaset must be specified to bootstrap an instance")
}
} else {
if ctx.Replicaset != "" {
return fmt.Errorf(
"the replicaset can not be specified in the case of application bootstrapping")
}
}

orchestratorType, err := getApplicationOrchestrator(ctx.Orchestrator,
ctx.RunningCtx)
if err != nil {
return err
}

orchestrator, err := makeApplicationOrchestrator(orchestratorType,
ctx.RunningCtx, nil, nil)
if err != nil {
return err
}

err = orchestrator.Bootstrap(replicaset.BootstrapCtx{
ReplicasetsFile: ctx.ReplicasetsFile,
Timeout: ctx.Timeout,
Instance: ctx.Instance,
Replicaset: ctx.Replicaset,
BootstrapVShard: ctx.BootstrapVShard,
})
if err == nil {
// Re-discovery and log topology.
replicasets, err := orchestrator.Discovery(replicaset.SkipCache)
if err != nil {
return err
}
statusReplicasets(replicasets)
fmt.Println()
log.Info("Done.")
}
return err
}
1 change: 1 addition & 0 deletions cli/replicaset/cmd/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type replicasetOrchestrator interface {
replicaset.Demoter
replicaset.Expeller
replicaset.VShardBootstrapper
replicaset.Bootstrapper
}

// makeApplicationOrchestrator creates an orchestrator for the application.
Expand Down
124 changes: 124 additions & 0 deletions test/integration/replicaset/test_replicaset_bootstrap.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import os
import re
import shutil

import pytest

from utils import get_tarantool_version, run_command_and_get_output, wait_file

tarantool_major_version, tarantool_minor_version = get_tarantool_version()


@pytest.mark.skipif(tarantool_major_version > 2,
reason="skip custom test for Tarantool > 2")
@pytest.mark.parametrize("case", [["--config", "--custom"],
["--custom", "--cartridge"],
["--config", "--cartridge"],
["--config", "--custom", "--cartridge"]])
def test_bootstrap(tt_cmd, tmpdir_with_cfg, case):
cmd = [tt_cmd, "rs", "bootstrap"] + case + ["app:instance"]
rc, out = run_command_and_get_output(cmd, cwd=tmpdir_with_cfg)
assert rc == 1
assert re.search(r" ⨯ only one type of orchestrator can be forced", out)


@pytest.mark.skipif(tarantool_major_version > 2,
reason="skip custom test for Tarantool > 2")
def test_bootstrap_no_instance(tt_cmd, tmpdir_with_cfg):
tmpdir = tmpdir_with_cfg
app_name = "test_custom_app"
app_path = os.path.join(tmpdir, app_name)
shutil.copytree(os.path.join(os.path.dirname(__file__), app_name), app_path)

status_cmd = [tt_cmd, "rs", "bootstrap", "test_custom_app:unexist"]
rc, out = run_command_and_get_output(status_cmd, cwd=tmpdir_with_cfg)
assert rc == 1
assert re.search(r" ⨯ instance \"unexist\" not found", out)


@pytest.mark.skipif(tarantool_major_version > 2,
reason="skip custom test for Tarantool > 2")
@pytest.mark.parametrize("flag", [None, "--custom"])
def test_bootstrap_custom_app(tt_cmd, tmpdir_with_cfg, flag):
tmpdir = tmpdir_with_cfg
app_name = "test_custom_app"
app_path = os.path.join(tmpdir, app_name)
shutil.copytree(os.path.join(os.path.dirname(__file__), app_name), app_path)
try:
# Start a cluster.
start_cmd = [tt_cmd, "start", app_name]
rc, out = run_command_and_get_output(start_cmd, cwd=tmpdir)
assert rc == 0

# Check for start.
file = wait_file(os.path.join(tmpdir, app_name), 'ready', [])
assert file != ""

cmd = [tt_cmd, "rs", "bootstrap"]
if flag:
cmd.append(flag)
cmd.append("test_custom_app")

rc, out = run_command_and_get_output(cmd, cwd=tmpdir)
assert rc == 1
expected = '⨯ bootstrap is not supported for an application by "custom" orchestrator'
assert expected in out
finally:
stop_cmd = [tt_cmd, "stop", app_name]
rc, _ = run_command_and_get_output(stop_cmd, cwd=tmpdir)
assert rc == 0


@pytest.mark.skipif(tarantool_major_version > 2,
reason="skip custom test for Tarantool > 2")
def test_bootstrap_instance_no_replicaset_specified(tt_cmd, tmpdir_with_cfg):
tmpdir = tmpdir_with_cfg
app_name = "test_custom_app"
app_path = os.path.join(tmpdir, app_name)
shutil.copytree(os.path.join(os.path.dirname(__file__), app_name), app_path)
try:
# Start a cluster.
start_cmd = [tt_cmd, "start", app_name]
rc, out = run_command_and_get_output(start_cmd, cwd=tmpdir)
assert rc == 0

# Check for start.
file = wait_file(os.path.join(tmpdir, app_name), 'ready', [])
assert file != ""

cmd = [tt_cmd, "rs", "bootstrap", "test_custom_app:test_custom_app"]
rc, out = run_command_and_get_output(cmd, cwd=tmpdir)
assert rc != 0
assert "⨯ the replicaset must be specified to bootstrap an instance" in out
finally:
stop_cmd = [tt_cmd, "stop", app_name]
rc, _ = run_command_and_get_output(stop_cmd, cwd=tmpdir)
assert rc == 0


@pytest.mark.skipif(tarantool_major_version > 2,
reason="skip custom test for Tarantool > 2")
def test_bootstrap_app_replicaset_specified(tt_cmd, tmpdir_with_cfg):
tmpdir = tmpdir_with_cfg
app_name = "test_custom_app"
app_path = os.path.join(tmpdir, app_name)
shutil.copytree(os.path.join(os.path.dirname(__file__), app_name), app_path)
try:
# Start a cluster.
start_cmd = [tt_cmd, "start", app_name]
rc, out = run_command_and_get_output(start_cmd, cwd=tmpdir)
assert rc == 0

# Check for start.
file = wait_file(os.path.join(tmpdir, app_name), 'ready', [])
assert file != ""

cmd = [tt_cmd, "rs", "bootstrap", "--replicaset", "r1", "test_custom_app"]
rc, out = run_command_and_get_output(cmd, cwd=tmpdir)
assert rc != 0
expected = "⨯ the replicaset can not be specified in the case of application bootstrapping"
assert expected in out
finally:
stop_cmd = [tt_cmd, "stop", app_name]
rc, _ = run_command_and_get_output(stop_cmd, cwd=tmpdir)
assert rc == 0

0 comments on commit 8ecb32a

Please sign in to comment.