Skip to content

Commit

Permalink
connect: add binary port connection to instance
Browse files Browse the repository at this point in the history
Added ability to connect to instance using binary port.

Follow up #574
  • Loading branch information
better0fdead authored and psergee committed Mar 7, 2024
1 parent 1262c21 commit 5a4544e
Show file tree
Hide file tree
Showing 10 changed files with 332 additions and 10 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ running apps.

### Added

- `tt connect --binary`: connect to instance using binary port.
- `tt env`: add current environment binaries location to the PATH variable.
- `tt cluster`: add an ability to specify a key for `show`/`publish` via URI.
- `tt cluster`: add an ability to publish/show configuration from tarantool
Expand Down
18 changes: 14 additions & 4 deletions cli/cmd/connect.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ var (
connectSslCaFile string
connectSslCiphers string
connectInteractive bool
connectBinary bool
)

// NewConnectCmd creates connect command.
Expand Down Expand Up @@ -101,6 +102,8 @@ func NewConnectCmd() *cobra.Command {
`colon-separated (:) list of SSL cipher suites the connection`)
connectCmd.Flags().BoolVarP(&connectInteractive, "interactive", "i",
false, `enter interactive mode after executing 'FILE'`)
connectCmd.Flags().BoolVarP(&connectBinary, "binary", "",
false, `connect to instance using binary port`)

return connectCmd
}
Expand Down Expand Up @@ -138,14 +141,20 @@ func resolveConnectOpts(cmdCtx *cmdcontext.CmdCtx, cliOpts *config.CliOpts,
err = fmt.Errorf("specify instance name")
return
}
if connectCtx.Username != "" || connectCtx.Password != "" {
if (connectCtx.Username != "" || connectCtx.Password != "") && !connectCtx.Binary {
err = fmt.Errorf("username and password are not supported" +
" with a connection via a control socket")
return
}
connOpts = makeConnOpts(
connector.UnixNetwork, runningCtx.Instances[0].ConsoleSocket, *connectCtx,
)
if connectCtx.Binary {
connOpts = makeConnOpts(
connector.UnixNetwork, runningCtx.Instances[0].BinaryPort, *connectCtx,
)
} else {
connOpts = makeConnOpts(
connector.UnixNetwork, runningCtx.Instances[0].ConsoleSocket, *connectCtx,
)
}
} else if connect.IsCredentialsURI(args[0]) {
if connectCtx.Username != "" || connectCtx.Password != "" {
err = fmt.Errorf("username and password are specified with" +
Expand Down Expand Up @@ -189,6 +198,7 @@ func internalConnectModule(cmdCtx *cmdcontext.CmdCtx, args []string) error {
SslCaFile: connectSslCaFile,
SslCiphers: connectSslCiphers,
Interactive: connectInteractive,
Binary: connectBinary,
}

var ok bool
Expand Down
2 changes: 2 additions & 0 deletions cli/connect/connect.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ type ConnectCtx struct {
Interactive bool
// ConnectTarget contains connection target string: URI or instance name.
ConnectTarget string
// Binary port is used
Binary bool
}

const (
Expand Down
277 changes: 273 additions & 4 deletions test/integration/connect/test_connect.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import os
import platform
import re
import shutil
import subprocess
import tempfile

import psutil
import pytest

from utils import (control_socket, get_tarantool_version, kill_procs,
run_command_and_get_output, run_path, wait_file)
from utils import (control_socket, create_tt_config, get_tarantool_version,
kill_procs, run_command_and_get_output, run_path, wait_file)

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


@pytest.fixture(autouse=True)
Expand All @@ -33,15 +36,21 @@ def copy_data(dst, file_paths):
shutil.copy(path, dst)


def start_app(tt_cmd, tmpdir_with_cfg, app_name):
def start_app(tt_cmd, tmpdir_with_cfg, app_name, start_binary_port=False):
test_env = os.environ.copy()
# Set empty TT_LISTEN, so no binary port will be created.
if start_binary_port is False:
test_env['TT_LISTEN'] = ''

# Start an instance.
start_cmd = [tt_cmd, "start", app_name]
instance_process = subprocess.Popen(
start_cmd,
cwd=tmpdir_with_cfg,
stderr=subprocess.STDOUT,
stdout=subprocess.PIPE,
text=True
text=True,
env=test_env
)
start_output = instance_process.stdout.readline()
assert re.search(r"Starting an instance", start_output)
Expand Down Expand Up @@ -133,6 +142,12 @@ def is_language_supported(tt_cmd, tmpdir):
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_tarantool_ee():
cmd = ["tarantool", "--version"]
instance_process = subprocess.run(
Expand Down Expand Up @@ -176,6 +191,11 @@ def skip_if_tarantool_ce():
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 test_connect_and_get_commands_outputs(tt_cmd, tmpdir_with_cfg):
tmpdir = tmpdir_with_cfg
empty_file = "empty.lua"
Expand Down Expand Up @@ -1578,3 +1598,252 @@ def test_output_format_tables_dialects(tt_cmd, tmpdir_with_cfg):

# Stop the Instance.
stop_app(tt_cmd, tmpdir, "test_app")


def test_connect_to_single_instance_app_binary(tt_cmd):
if platform.system() == "Darwin":
pytest.skip("/set platform is unsupported by test")
tmpdir = tempfile.mkdtemp()
create_tt_config(tmpdir, "")
empty_file = "empty.lua"
# The test application file.
test_app_path = os.path.join(os.path.dirname(__file__), "test_single_app", "test_app.lua")
# The test file.
empty_file_path = os.path.join(os.path.dirname(__file__), "test_file", empty_file)
# Copy test data into temporary directory.
copy_data(tmpdir, [test_app_path, empty_file_path])

# Start an instance.
start_app(tt_cmd, tmpdir, "test_app", True)

# Check for start.
file = wait_file(os.path.join(tmpdir, "test_app", run_path, "test_app"),
BINARY_PORT_NAME, [])
assert file != ""
file = wait_file(os.path.join(tmpdir, 'test_app'), 'configured', [])
assert file != ""

# Remove console socket
os.remove(os.path.join(tmpdir, "test_app", run_path, "test_app", "tarantool.control"))

# Connect to the instance and execute a script.
try:
connect_cmd = [tt_cmd, "connect", "test_app", "--binary", "-u", "test", "-p", "password",
"-f", empty_file]
instance_process = subprocess.run(
connect_cmd,
cwd=tmpdir,
stderr=subprocess.STDOUT,
stdout=subprocess.PIPE,
text=True,
)
assert instance_process.returncode == 0

# Connect to the instance and execute stdin.
connect_cmd = [tt_cmd, "connect", "test_app", "--binary", "-u", "test", "-p", "password"]
instance_process = subprocess.run(
connect_cmd,
cwd=tmpdir,
stderr=subprocess.STDOUT,
stdout=subprocess.PIPE,
text=True,
input="2+2"
)
assert instance_process.returncode == 0
assert instance_process.stdout == "---\n- 4\n...\n\n"
finally:
# Stop the Instance.
stop_app(tt_cmd, tmpdir, "test_app")
shutil.rmtree(tmpdir)


def test_connect_to_multi_instances_app_binary(tt_cmd):
if platform.system() == "Darwin":
pytest.skip("/set platform is unsupported by test")
tmpdir = tempfile.mkdtemp()
create_tt_config(tmpdir, "")
app_name = "test_multi_app"
empty_file = "empty.lua"
# Copy the test application to the "run" directory.
test_app_path = os.path.join(os.path.dirname(__file__), app_name)
tmp_app_path = os.path.join(tmpdir, app_name)
shutil.copytree(test_app_path, tmp_app_path)
# The test file.
empty_file_path = os.path.join(os.path.dirname(__file__), "test_file", empty_file)
# Copy test data into temporary directory.
copy_data(tmpdir, [empty_file_path])

# Start instances.
start_app(tt_cmd, tmpdir, app_name, True)
try:
# Check for start.
instances = ['master', 'replica', 'router']
for instance in instances:
master_run_path = os.path.join(tmpdir, app_name, run_path, instance)
file = wait_file(master_run_path, control_socket, [])
assert file != ""
file = wait_file(master_run_path, BINARY_PORT_NAME, [])
assert file != ""
file = wait_file(os.path.join(tmpdir, app_name), instance, [])
assert file != ""

# Connect to the instance and execute stdin.
connect_cmd = [tt_cmd, "connect", app_name + ":master", "--binary",
"-u", "test", "-p", "password"]
instance_process = subprocess.run(
connect_cmd,
cwd=tmpdir,
stderr=subprocess.STDOUT,
stdout=subprocess.PIPE,
text=True,
input="2+2"
)
assert instance_process.returncode == 0
assert instance_process.stdout == "---\n- 4\n...\n\n"
finally:
# Stop the Instance.
stop_app(tt_cmd, tmpdir, app_name)
shutil.rmtree(tmpdir)


def test_connect_to_instance_binary_missing_port(tt_cmd):
if platform.system() == "Darwin":
pytest.skip("/set platform is unsupported by test")
tmpdir = tempfile.mkdtemp()
create_tt_config(tmpdir, "")
empty_file = "empty.lua"
# The test application file.
test_app_path = os.path.join(os.path.dirname(__file__), "test_single_app", "test_app.lua")
# The test file.
empty_file_path = os.path.join(os.path.dirname(__file__), "test_file", empty_file)
# Copy test data into temporary directory.
copy_data(tmpdir, [test_app_path, empty_file_path])

# Start an instance.
start_app(tt_cmd, tmpdir, "test_app", True)

# Check for start.
file = wait_file(os.path.join(tmpdir, "test_app", run_path, "test_app"),
BINARY_PORT_NAME, [])
assert file != ""
file = wait_file(os.path.join(tmpdir, 'test_app'), 'configured', [])
assert file != ""

# Remove binary port.
os.remove(os.path.join(tmpdir, "test_app", run_path, "test_app", BINARY_PORT_NAME))

try:
# Connect to the instance and execute stdin.
connect_cmd = [tt_cmd, "connect", "test_app", "--binary", "-u", "test", "-p", "password"]
instance_process = subprocess.run(
connect_cmd,
cwd=tmpdir,
stderr=subprocess.STDOUT,
stdout=subprocess.PIPE,
text=True,
input="2+2"
)
assert instance_process.returncode == 1
finally:
# Stop the Instance.
stop_app(tt_cmd, tmpdir, "test_app")
shutil.rmtree(tmpdir)


def test_connect_to_instance_binary_port_is_broken(tt_cmd):
if platform.system() == "Darwin":
pytest.skip("/set platform is unsupported by test")
tmpdir = tempfile.mkdtemp()
create_tt_config(tmpdir, "")
empty_file = "empty.lua"
# The test application file.
test_app_path = os.path.join(os.path.dirname(__file__), "test_single_app", "test_app.lua")
# The test file.
empty_file_path = os.path.join(os.path.dirname(__file__), "test_file", empty_file)
# Copy test data into temporary directory.
copy_data(tmpdir, [test_app_path, empty_file_path])

# Start an instance.
start_app(tt_cmd, tmpdir, "test_app", True)

# Check for start.
file = wait_file(os.path.join(tmpdir, "test_app", run_path, "test_app"),
BINARY_PORT_NAME, [])
assert file != ""
file = wait_file(os.path.join(tmpdir, 'test_app'), 'configured', [])
assert file != ""

# Remove binary port.
os.remove(os.path.join(tmpdir, "test_app", run_path, "test_app", BINARY_PORT_NAME))
# Create fake binary port.
fake_binary_port = open(os.path.join(tmpdir, "test_app", run_path,
"test_app", BINARY_PORT_NAME), "a")
fake_binary_port.write("I am totally not a binary port.")
fake_binary_port.close()

try:
# Connect to the instance and execute stdin.
connect_cmd = [tt_cmd, "connect", "test_app", "--binary", "-u", "test", "-p", "password"]
instance_process = subprocess.run(
connect_cmd,
cwd=tmpdir,
stderr=subprocess.STDOUT,
stdout=subprocess.PIPE,
text=True,
input="2+2"
)
assert instance_process.returncode == 1
finally:
# Stop the Instance.
stop_app(tt_cmd, tmpdir, "test_app")
shutil.rmtree(tmpdir)


def test_connect_to_cluster_app(tt_cmd):
if platform.system() == "Darwin":
pytest.skip("/set platform is unsupported by test")
tmpdir = tempfile.mkdtemp()
create_tt_config(tmpdir, "")
skip_if_cluster_app_unsupported(tt_cmd, tmpdir)

empty_file = "empty.lua"
app_name = "test_simple_cluster_app"
# Copy the test application to the "run" directory.
test_app_path = os.path.join(os.path.dirname(__file__), app_name)
tmp_app_path = os.path.join(tmpdir, app_name)
shutil.copytree(test_app_path, tmp_app_path)
# The test file.
empty_file_path = os.path.join(os.path.dirname(__file__), "test_file", empty_file)
# Copy test data into temporary directory.
copy_data(tmpdir, [empty_file_path])

# Start instances.
start_app(tt_cmd, tmpdir, app_name, True)
try:
# Check for start.
instances = ['master']
for instance in instances:
master_run_path = os.path.join(tmpdir, app_name, run_path, instance)
file = wait_file(master_run_path, control_socket, [])
assert file != ""
file = wait_file(master_run_path, BINARY_PORT_NAME, [])
assert file != ""
file = wait_file(os.path.join(tmpdir, app_name), 'configured', [])
assert file != ""

# Connect to the instance and execute stdin.
connect_cmd = [tt_cmd, "connect", app_name + ":master", "--binary"]
instance_process = subprocess.run(
connect_cmd,
cwd=tmpdir,
stderr=subprocess.STDOUT,
stdout=subprocess.PIPE,
text=True,
input="2+2"
)
assert instance_process.returncode == 0
assert instance_process.stdout == "---\n- 4\n...\n\n"
finally:
# Stop the Instance.
stop_app(tt_cmd, tmpdir, app_name)
shutil.rmtree(tmpdir)
Loading

0 comments on commit 5a4544e

Please sign in to comment.