[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