[Tarantool-patches] [PATCH] Add options for upgrade testing
sergeyb at tarantool.org
sergeyb at tarantool.org
Wed Nov 18 12:30:49 MSK 2020
From: Sergey Bronnikov <sergeyb at tarantool.org>
Option '--snapshot' specifies a path to snapshot that will be loaded in
Tarantool before testing.
Option '--disable-schema-upgrade' allows to skip execution
of Tarantool upgrade script using error injection mechanism.
Closes https://github.com/tarantool/tarantool/issues/4801
---
lib/__init__.py | 4 ++++
lib/app_server.py | 15 +++++++++++++-
lib/options.py | 31 +++++++++++++++++++++++++++++
lib/server.py | 9 +++++++++
lib/tarantool_server.py | 44 ++++++++++++++++++++++++++++++++++++-----
lib/utils.py | 37 ++++++++++++++++++++++++++++++++++
6 files changed, 134 insertions(+), 6 deletions(-)
diff --git a/lib/__init__.py b/lib/__init__.py
index 398439d..a3d1597 100644
--- a/lib/__init__.py
+++ b/lib/__init__.py
@@ -6,6 +6,7 @@ from lib.options import Options
from lib.tarantool_server import TarantoolServer
from lib.unittest_server import UnittestServer
from utils import warn_unix_sockets_at_start
+from utils import test_debug
__all__ = ['Options']
@@ -56,6 +57,9 @@ def module_init():
TarantoolServer.find_exe(args.builddir)
UnittestServer.find_exe(args.builddir)
+ is_debug = test_debug(TarantoolServer().version())
+ Options().check_schema_upgrade_option(is_debug)
+
# Init
######
diff --git a/lib/app_server.py b/lib/app_server.py
index 2cb8a87..706ae9c 100644
--- a/lib/app_server.py
+++ b/lib/app_server.py
@@ -9,11 +9,13 @@ from gevent.subprocess import Popen, PIPE
from lib.colorer import color_log
from lib.preprocessor import TestState
from lib.server import Server
+from lib.server import DEFAULT_SNAPSHOT_NAME
from lib.tarantool_server import Test
from lib.tarantool_server import TarantoolServer
from lib.tarantool_server import TarantoolStartError
from lib.utils import find_port
from lib.utils import format_process
+from lib.utils import safe_makedirs
from lib.utils import warn_unix_socket
from test import TestRunGreenlet, TestExecutionError
@@ -86,7 +88,12 @@ class AppServer(Server):
return os.path.join(self.vardir, file_name)
def prepare_args(self, args=[]):
- return [os.path.join(os.getcwd(), self.current_test.name)] + args
+ cli_args = [os.path.join(os.getcwd(), self.current_test.name)] + args
+ if self.disable_schema_upgrade:
+ cli_args = [self.binary, '-e',
+ self.DISABLE_AUTO_UPGRADE] + cli_args
+
+ return cli_args
def deploy(self, vardir=None, silent=True, need_init=True):
self.vardir = vardir
@@ -119,6 +126,12 @@ class AppServer(Server):
# cannot check length of path of *.control unix socket created by it.
# So for 'app' tests type we don't check *.control unix sockets paths.
+ if self.snapshot_path:
+ snapshot_dest = os.path.join(self.vardir, DEFAULT_SNAPSHOT_NAME)
+ color_log("Copying snapshot {} to {}\n".format(
+ self.snapshot_path, snapshot_dest))
+ shutil.copy(self.snapshot_path, snapshot_dest)
+
def stop(self, silent):
if not self.process:
return
diff --git a/lib/options.py b/lib/options.py
index 47bbc0f..9dcb70e 100644
--- a/lib/options.py
+++ b/lib/options.py
@@ -209,6 +209,19 @@ class Options:
help="""Update or create file with reference output (.result).
Default: false.""")
+ parser.add_argument(
+ "--snapshot",
+ dest='snapshot_path',
+ help="""Path to a Tarantool snapshot that will be
+ loaded before testing.""")
+
+ parser.add_argument(
+ "--disable-schema-upgrade",
+ dest='disable_schema_upgrade',
+ action="store_true",
+ default=False,
+ help="""Disable schema upgrade on testing with snapshots.""")
+
# XXX: We can use parser.parse_intermixed_args() on
# Python 3.7 to understand commands like
# ./test-run.py foo --exclude bar baz
@@ -227,5 +240,23 @@ class Options:
color_stdout(format_str.format(op1, op2), schema='error')
check_error = True
+ snapshot_path = getattr(self.args, 'snapshot_path', '')
+ if self.args.disable_schema_upgrade and not snapshot_path:
+ color_stdout("\nOption --disable-schema-upgrade requires --snapshot\n",
+ schema='error')
+ check_error = True
+
+ if snapshot_path:
+ if not os.path.exists(snapshot_path):
+ color_stdout("\nPath {} not exists\n".format(snapshot_path), schema='error')
+ check_error = True
+ else:
+ self.args.snapshot_path = os.path.abspath(snapshot_path)
+
if check_error:
exit(-1)
+
+ def check_schema_upgrade_option(self, is_debug):
+ if self.args.disable_schema_upgrade and not is_debug:
+ color_stdout("Can't disable schema upgrade on release build\n", schema='error')
+ exit(-1)
diff --git a/lib/server.py b/lib/server.py
index 321eac7..f0fb03a 100644
--- a/lib/server.py
+++ b/lib/server.py
@@ -9,12 +9,14 @@ from lib.server_mixins import LLdbMixin
from lib.server_mixins import StraceMixin
from lib.server_mixins import LuacovMixin
from lib.colorer import color_stdout
+from lib.options import Options
from lib.utils import print_tail_n
DEFAULT_CHECKPOINT_PATTERNS = ["*.snap", "*.xlog", "*.vylog", "*.inprogress",
"[0-9]*/"]
+DEFAULT_SNAPSHOT_NAME = "00000000000000000000.snap"
class Server(object):
"""Server represents a single server instance. Normally, the
@@ -24,6 +26,10 @@ class Server(object):
DEFAULT_INSPECTOR = 0
TEST_RUN_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__),
".."))
+ # assert(false) hangs due to gh-4983, added fiber.sleep(0) to workaround it
+ DISABLE_AUTO_UPGRADE = "require('fiber').sleep(0) \
+ assert(box.error.injection.set('ERRINJ_AUTO_UPGRADE', true) == 'ok', \
+ 'no such errinj')"
@property
def vardir(self):
@@ -84,6 +90,9 @@ class Server(object):
self.inspector_port = int(ini.get(
'inspector_port', self.DEFAULT_INSPECTOR
))
+ self.testdir = os.path.abspath(os.curdir)
+ self.disable_schema_upgrade = Options().args.disable_schema_upgrade
+ self.snapshot_path = Options().args.snapshot_path
# filled in {Test,AppTest,LuaTest,PythonTest}.execute()
# or passed through execfile() for PythonTest (see
diff --git a/lib/tarantool_server.py b/lib/tarantool_server.py
index fd102b9..17b6f44 100644
--- a/lib/tarantool_server.py
+++ b/lib/tarantool_server.py
@@ -26,12 +26,16 @@ from lib.box_connection import BoxConnection
from lib.colorer import color_stdout, color_log
from lib.preprocessor import TestState
from lib.server import Server
+from lib.server import DEFAULT_SNAPSHOT_NAME
from lib.test import Test
from lib.utils import find_port
+from lib.utils import extract_schema_from_snapshot
from lib.utils import format_process
+from lib.utils import safe_makedirs
from lib.utils import signame
from lib.utils import warn_unix_socket
from lib.utils import prefix_each_line
+from lib.utils import test_debug
from test import TestRunGreenlet, TestExecutionError
@@ -609,7 +613,6 @@ class TarantoolServer(Server):
}
ini.update(_ini)
Server.__init__(self, ini, test_suite)
- self.testdir = os.path.abspath(os.curdir)
self.sourcedir = os.path.abspath(os.path.join(os.path.basename(
sys.argv[0]), "..", ".."))
self.name = "default"
@@ -775,8 +778,29 @@ class TarantoolServer(Server):
shutil.copy(os.path.join(self.TEST_RUN_DIR, 'pretest_clean.lua'),
self.vardir)
+ if self.snapshot_path:
+ # Copy snapshot to the workdir.
+ # Usually Tarantool looking for snapshots on start in a current directory
+ # or in a directories that specified in memtx_dir or vinyl_dir box settings.
+ # Before running test current directory (workdir) passed to a new instance in
+ # an environment variable TEST_WORKDIR and then .tarantoolctl.in
+ # adds to it instance_name and set to memtx_dir and vinyl_dir.
+ (instance_name, _) = os.path.splitext(os.path.basename(self.script))
+ instance_dir = os.path.join(self.vardir, instance_name)
+ safe_makedirs(instance_dir)
+ snapshot_dest = os.path.join(instance_dir, DEFAULT_SNAPSHOT_NAME)
+ color_log("Copying snapshot {} to {}\n".format(
+ self.snapshot_path, snapshot_dest))
+ shutil.copy(self.snapshot_path, snapshot_dest)
+
def prepare_args(self, args=[]):
- return [self.ctl_path, 'start', os.path.basename(self.script)] + args
+ cli_args = [self.ctl_path, 'start',
+ os.path.basename(self.script)] + args
+ if self.disable_schema_upgrade:
+ cli_args = [self.binary, '-e',
+ self.DISABLE_AUTO_UPGRADE] + cli_args
+
+ return cli_args
def pretest_clean(self):
# Don't delete snap and logs for 'default' tarantool server
@@ -861,6 +885,18 @@ class TarantoolServer(Server):
self.admin = CON_SWITCH[self.tests_type]('localhost', port)
self.status = 'started'
+ if not self.ctl_path:
+ self.ctl_path = self.find_exe(self.builddir)
+ if self.disable_schema_upgrade:
+ version = extract_schema_from_snapshot(self.ctl_path,
+ self.builddir, self.snapshot_path)
+ current_ver = yaml.safe_load(self.admin.execute(
+ 'box.space._schema:get{"version"}'))
+ if version == current_ver:
+ raise_msg = 'Versions are inequal ({} vs {})'.format(
+ version, current_ver)
+ raise Exception(raise_msg)
+
def crash_detect(self):
if self.crash_expected:
return
@@ -1075,9 +1111,7 @@ class TarantoolServer(Server):
print self.test_option_get(option_list_str)
def test_debug(self):
- if re.findall(r"-Debug", self.test_option_get("-V", True), re.I):
- return True
- return False
+ return test_debug(self.test_option_get("-V", True))
@staticmethod
def find_tests(test_suite, suite_path):
diff --git a/lib/utils.py b/lib/utils.py
index cc2f6b7..2aeedc5 100644
--- a/lib/utils.py
+++ b/lib/utils.py
@@ -1,10 +1,13 @@
import os
+import re
import sys
import six
import collections
import signal
+import subprocess
import random
import fcntl
+import json
import difflib
import time
from gevent import socket
@@ -264,3 +267,37 @@ def just_and_trim(src, width):
if len(src) > width:
return src[:width - 1] + '>'
return src.ljust(width)
+
+
+def extract_schema_from_snapshot(ctl_path, builddir, snapshot_path):
+ """
+ Extract schema version from snapshot, example of record:
+ {"HEADER":{"lsn":2,"type":"INSERT","timestamp":1584694286.0031},
+ "BODY":{"space_id":272,"tuple":["version",2,3,1]}}
+ """
+ SCHEMA_SPACE_ID = 272
+
+ ctl_args = [ctl_path, 'cat', snapshot_path,
+ '--format=json', '--show-system']
+ proc = subprocess.Popen(ctl_args, stdout=subprocess.PIPE)
+ version = None
+ while True:
+ line = proc.stdout.readline()
+ if not line:
+ break
+ json_line = json.loads(line.strip())
+ query_type = json_line["HEADER"]["type"]
+ space_id = json_line["BODY"]["space_id"]
+ if query_type == "INSERT" or query_type == "REPLACE" and space_id == SCHEMA_SPACE_ID:
+ query_tuple = json_line['BODY']['tuple']
+ if query_tuple[0] == 'version':
+ version = query_tuple
+ break
+
+ return version
+
+
+def test_debug(version):
+ if re.findall(r"-Debug", version, re.I):
+ return True
+ return False
--
2.25.1
More information about the Tarantool-patches
mailing list