Skip to content

Commit

Permalink
play: support app:instance
Browse files Browse the repository at this point in the history
Closes #1022

@TarantoolBot document
Title: `tt play` command app:instance support

This patch adds support connection to a target instance
by `app:instance` for `tt play` command.
  • Loading branch information
patapenka-alexey authored and dmyger committed Dec 13, 2024
1 parent 877f3af commit 2003f96
Show file tree
Hide file tree
Showing 12 changed files with 293 additions and 115 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- `tt.yaml`: allows to specify a list of modules directories.
- Environment variable TT_CLI_MODULES_PATH can be used to specify
an extra path with modules.
- `tt play`: support connection to a target instance by `application` name
or `application:instance` name.

### Changed

Expand Down
64 changes: 45 additions & 19 deletions cli/cmd/play.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/tarantool/tt/cli/checkpoint"
"github.com/tarantool/tt/cli/cmdcontext"
"github.com/tarantool/tt/cli/modules"
"github.com/tarantool/tt/cli/running"
"github.com/tarantool/tt/cli/util"
"github.com/tarantool/tt/cli/version"
libconnect "github.com/tarantool/tt/lib/connect"
Expand All @@ -39,18 +40,18 @@ var (
// NewPlayCmd creates a new play command.
func NewPlayCmd() *cobra.Command {
var playCmd = &cobra.Command{
Use: "play <URI> <FILE>...",
Use: "play (<URI> | <APP_NAME> | <APP_NAME:INSTANCE_NAME>) <FILE>...",
Short: "Play the contents of .snap/.xlog FILE(s) to another Tarantool instance",
Run: func(cmd *cobra.Command, args []string) {
cmdCtx.CommandName = cmd.Name()
err := modules.RunCmd(&cmdCtx, cmd.CommandPath(), &modulesInfo,
internalPlayModule, args)
util.HandleCmdErr(cmd, err)
},
Example: "tt play uri /path/to/file.snap /path/to/file.xlog /path/to/dir/ " +
"--timestamp 2024-11-13T14:02:36.818700000+00:00\n" +
" tt play uri /path/to/file.snap /path/to/file.xlog /path/to/dir/ " +
"--timestamp=1731592956.818",
Example: "tt play localhost:3013 /path/to/file.snap /path/to/file.xlog " +
"/path/to/dir/ --timestamp 2024-11-13T14:02:36.818700000+00:00\n" +
" tt play app:instance001 /path/to/file.snap /path/to/file.xlog " +
"/path/to/dir/ --timestamp=1731592956.818",
}

playCmd.Flags().StringVarP(&playUsername, "username", "u", "", "username")
Expand Down Expand Up @@ -78,6 +79,45 @@ func internalPlayModule(cmdCtx *cmdcontext.CmdCtx, args []string) error {
"or directory")
}

// FillCtx returns error if no instances found.
var runningCtx running.RunningCtx

if err := running.FillCtx(cliOpts, cmdCtx, &runningCtx, []string{args[0]}, false); err == nil {
if len(runningCtx.Instances) > 1 {
return util.InternalError(
"Internal error: specify instance name",
version.GetVersion)
}

_, err := os.Stat(runningCtx.Instances[0].BinaryPort)
if err != nil {
return util.InternalError(
"Internal error: application binary port does not exist: %s",
version.GetVersion, err)
}

args[0] = runningCtx.Instances[0].BinaryPort
} else if libconnect.IsCredentialsURI(args[0]) {
if playUsername != "" || playPassword != "" {
return errors.New("username and password are specified with" +
" flags and a URI")
}
uri, user, pass := libconnect.ParseCredentialsURI(args[0])
playUsername = user
playPassword = pass
args[0] = uri
} else if libconnect.IsBaseURI(args[0]) {
if playUsername == "" {
playUsername = os.Getenv(libconnect.TarantoolUsernameEnv)
}
if playPassword == "" {
playPassword = os.Getenv(libconnect.TarantoolPasswordEnv)
}
} else {
return util.InternalError("could not resolve URI or application: %q (%s)",
version.GetVersion, args[0], err)
}

walFiles, err := util.CollectWALFiles(args[1:])
if err != nil {
return util.InternalError(
Expand All @@ -96,20 +136,6 @@ func internalPlayModule(cmdCtx *cmdcontext.CmdCtx, args []string) error {
version.GetVersion, err)
}

if libconnect.IsCredentialsURI(args[0]) {
if playUsername != "" || playPassword != "" {
return errors.New("username and password are specified with" +
" flags and a URI")
}
} else {
if playUsername == "" {
playUsername = os.Getenv(libconnect.TarantoolUsernameEnv)
}
if playPassword == "" {
playPassword = os.Getenv(libconnect.TarantoolPasswordEnv)
}
}

os.Setenv("TT_CLI_PLAY_FILES_AND_URI", string(filesAndUriJson))
if playUsername != "" {
os.Setenv("TT_CLI_PLAY_USERNAME", playUsername)
Expand Down
113 changes: 18 additions & 95 deletions test/integration/connect/test_connect.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@
import psutil
import pytest

from utils import (control_socket, create_tt_config, get_tarantool_version,
is_tarantool_ee, kill_procs, run_command_and_get_output,
run_path, wait_file)
from utils import (BINARY_PORT_NAME, control_socket, create_tt_config,
get_tarantool_version, is_tarantool_major_one, kill_procs,
run_command_and_get_output, run_path,
skip_if_cluster_app_unsupported, skip_if_quit_unsupported,
skip_if_tarantool_ce, skip_if_tuple_format_supported,
skip_if_tuple_format_unsupported, wait_file)

tarantool_major_version, tarantool_minor_version = get_tarantool_version()
BINARY_PORT_NAME = "tarantool.sock"


@pytest.fixture(autouse=True)
Expand Down Expand Up @@ -122,102 +124,23 @@ def prepare_test_app_languages(tt_cmd, tmpdir):
return "test_app", lua_file, sql_file


def get_version(tt_cmd, tmpdir):
run_cmd = [tt_cmd, "run", "-v"]
instance_process = subprocess.run(
run_cmd,
cwd=tmpdir,
stderr=subprocess.STDOUT,
stdout=subprocess.PIPE,
text=True
)
if instance_process.returncode == 0:
stdout = instance_process.stdout
full = stdout.splitlines()[0]
for word in re.split(r'\s', full):
matched = re.match(r'^\d+\.\d+\.\d+', word)
if matched:
print("Matched:")
print(matched)
version = re.split(r'\.', matched.group(0))
return True, int(version[0]), int(version[1]), int(version[2])
return False, 0, 0, 0


def is_quit_supported(tt_cmd, tmpdir):
ok, major, minor, patch = get_version(tt_cmd, tmpdir)
assert ok
return major >= 2


def is_language_supported(tt_cmd, tmpdir):
ok, major, minor, patch = get_version(tt_cmd, tmpdir)
assert ok
def is_language_supported():
major, minor = get_tarantool_version()
return major >= 2


def is_cluster_app_supported(tt_cmd, tmpdir):
ok, major, minor, patch = get_version(tt_cmd, tmpdir)
assert ok
return major >= 3


def is_tuple_format_supported(tt_cmd, tmpdir):
ok, major, minor, patch = get_version(tt_cmd, tmpdir)
assert ok
return major > 3 or (major == 3 and minor >= 2)


def is_tarantool_major_one():
cmd = ["tarantool", "--version"]
instance_process = subprocess.run(
cmd,
stderr=subprocess.STDOUT,
stdout=subprocess.PIPE,
text=True
)
if instance_process.returncode == 0:
return "Tarantool 1." in instance_process.stdout
return False


def skip_if_quit_unsupported(tt_cmd, tmpdir):
if not is_quit_supported(tt_cmd, tmpdir):
pytest.skip("\\q is unsupported")


def skip_if_language_unsupported(tt_cmd, tmpdir, test_app):
if not is_language_supported(tt_cmd, tmpdir):
if not is_language_supported():
stop_app(tt_cmd, tmpdir, test_app)
pytest.skip("\\set language is unsupported")


def skip_if_language_supported(tt_cmd, tmpdir, test_app):
if is_language_supported(tt_cmd, tmpdir):
if is_language_supported():
stop_app(tt_cmd, tmpdir, test_app)
pytest.skip("\\set language is supported")


def skip_if_tarantool_ce():
if not is_tarantool_ee():
pytest.skip("Tarantool Enterprise required")


def skip_if_cluster_app_unsupported(tt_cmd, tmpdir):
if not is_cluster_app_supported(tt_cmd, tmpdir):
pytest.skip("Tarantool 3.0 or above required")


def skip_if_tuple_format_supported(tt_cmd, tmpdir):
if is_tuple_format_supported(tt_cmd, tmpdir):
pytest.skip("Tuple format is supported")


def skip_if_tuple_format_unsupported(tt_cmd, tmpdir):
if not is_tuple_format_supported(tt_cmd, tmpdir):
pytest.skip("Tuple format is unsupported")


def test_connect_and_get_commands_outputs(tt_cmd, tmpdir_with_cfg):
tmpdir = tmpdir_with_cfg
empty_file = "empty.lua"
Expand Down Expand Up @@ -379,7 +302,7 @@ def test_connect_and_execute_quit(tt_cmd, tmpdir_with_cfg):
tmpdir = tmpdir_with_cfg
empty_file = "empty.lua"

skip_if_quit_unsupported(tt_cmd, tmpdir)
skip_if_quit_unsupported()

# The test application file.
test_app_path = os.path.join(os.path.dirname(__file__), "test_single_app", "test_app.lua")
Expand Down Expand Up @@ -1053,7 +976,7 @@ def test_output_format_lua(tt_cmd, tmpdir_with_cfg):


def test_lua_output_format_for_tuples(tt_cmd, tmpdir_with_cfg):
skip_if_tuple_format_unsupported(tt_cmd, tmpdir_with_cfg)
skip_if_tuple_format_unsupported()

tmpdir = tmpdir_with_cfg
# The test application file.
Expand Down Expand Up @@ -1162,7 +1085,7 @@ def test_lua_output_format_for_tuples(tt_cmd, tmpdir_with_cfg):


def test_yaml_output_format_for_tuples(tt_cmd, tmpdir_with_cfg):
skip_if_tuple_format_unsupported(tt_cmd, tmpdir_with_cfg)
skip_if_tuple_format_unsupported()

tmpdir = tmpdir_with_cfg
# The test application file.
Expand Down Expand Up @@ -1668,7 +1591,7 @@ def test_table_output_format(tt_cmd, tmpdir_with_cfg):


def test_table_output_format_for_tuples_no_format(tt_cmd, tmpdir_with_cfg):
skip_if_tuple_format_supported(tt_cmd, tmpdir_with_cfg)
skip_if_tuple_format_supported()

tmpdir = tmpdir_with_cfg
# The test application file.
Expand Down Expand Up @@ -1722,7 +1645,7 @@ def test_table_output_format_for_tuples_no_format(tt_cmd, tmpdir_with_cfg):


def test_table_output_format_for_tuples(tt_cmd, tmpdir_with_cfg):
skip_if_tuple_format_unsupported(tt_cmd, tmpdir_with_cfg)
skip_if_tuple_format_unsupported()

tmpdir = tmpdir_with_cfg
# The test application file.
Expand Down Expand Up @@ -2139,7 +2062,7 @@ def test_ttable_output_format(tt_cmd, tmpdir_with_cfg):


def test_ttable_output_format_for_tuples_no_format(tt_cmd, tmpdir_with_cfg):
skip_if_tuple_format_supported(tt_cmd, tmpdir_with_cfg)
skip_if_tuple_format_supported()

tmpdir = tmpdir_with_cfg
# The test application file.
Expand Down Expand Up @@ -2189,7 +2112,7 @@ def test_ttable_output_format_for_tuples_no_format(tt_cmd, tmpdir_with_cfg):


def test_ttable_output_format_for_tuples(tt_cmd, tmpdir_with_cfg):
skip_if_tuple_format_unsupported(tt_cmd, tmpdir_with_cfg)
skip_if_tuple_format_unsupported()

tmpdir = tmpdir_with_cfg
# The test application file.
Expand Down Expand Up @@ -2982,7 +2905,7 @@ def test_connect_to_cluster_app(tt_cmd):
pytest.skip("/set platform is unsupported by test")
tmpdir = tempfile.mkdtemp()
create_tt_config(tmpdir, "")
skip_if_cluster_app_unsupported(tt_cmd, tmpdir)
skip_if_cluster_app_unsupported()

empty_file = "empty.lua"
app_name = "test_simple_cluster_app"
Expand Down
12 changes: 12 additions & 0 deletions test/integration/play/test_file/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
app:
file: 'test_app.lua'

groups:
group001:
replicasets:
replicaset001:
instances:
instance001:
iproto:
listen:
- uri: '127.0.0.1:3301'
Empty file.
1 change: 1 addition & 0 deletions test/integration/play/test_file/instances.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
instance001:
16 changes: 16 additions & 0 deletions test/integration/play/test_file/test_app.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
local fiber = require('fiber')
local fio = require('fio')

box.cfg()

box.schema.user.create('test', { password = 'password' , if_not_exists = true })
box.schema.user.grant('test','read,write,execute,create,drop','universe')

box.schema.space.create('test', { id = 512 })
box.space.test:create_index('0')

fio.open('configured', 'O_CREAT'):close()

while true do
fiber.sleep(5)
end
Loading

0 comments on commit 2003f96

Please sign in to comment.