Tarantool/Box, or simply Tarantool, is a high performance key/value

storage server. The code is available for free under the terms of
BSD license. Supported platforms are GNU/Linux and FreeBSD.

WWW:	http://tarantool.org/

PR:		ports/163213
Submitted by:	Gvozdikov Veniamin <g.veniamin@googlemail.com>
This commit is contained in:
Eitan Adler 2012-01-02 15:41:28 +00:00
parent 2f2516d4df
commit 4ca7312e0e
Notes: svn2git 2021-03-31 03:12:20 +00:00
svn path=/head/; revision=288420
17 changed files with 1006 additions and 0 deletions

View file

@ -803,6 +803,7 @@
SUBDIR += squirrel-sql
SUBDIR += sybtcl
SUBDIR += tablelog
SUBDIR += tarantool
SUBDIR += tcl-Mysql
SUBDIR += tdb
SUBDIR += tinycdb

View file

@ -0,0 +1,43 @@
# New ports collection makefile for: tarantool
# Date created: 2011-11-25
# Whom: Gvozdikov Veniamin <g.veniamin@googlemail.com>
#
# $FreeBSD$
#
PORTNAME= tarantool
PORTVERSION= 1.3.5
CATEGORIES= databases
MASTER_SITES= http://launchpadlibrarian.net/71705094/
DISTNAME= ${PORTNAME}-${PORTVERSION}-src
MAINTAINER= g.veniamin@googlemail.com
COMMENT= Tarantool, is a high performance key/value storage server
LICENSE= BSD
ONLY_FOR_ARCHS= i386
USE_CMAKE= yes
ARCH= i386
USE_RC_SUBR= ${PORTNAME}
SUB_FILES= pkg-message
.include <bsd.port.pre.mk>
post-patch:
@${REINPLACE_CMD} -e 's|%%DOCSDIR%%|${DOCSDIR}|g' \
${WRKSRC}/CMakeLists.txt
@${REINPLACE_CMD} -e 's|%%ETCDIR%%|${ETCDIR}|g' \
${WRKSRC}/test/CMakeLists.txt
@${RM} ${WRKSRC}/test/lib/server.py \
${WRKSRC}/test/lib/silverbox.py \
${WRKSRC}/test/lib/tarantool_admin.py \
${WRKSRC}/test/lib/tarantool_feeder_server.py \
${WRKSRC}/test/lib/tarantool_server.py \
${WRKSRC}/test/lib/*.orig
post-install:
@${CAT} ${PKGMESSAGE}
.include <bsd.port.post.mk>

View file

@ -0,0 +1,2 @@
SHA256 (tarantool-1.3.5-src.tar.gz) = c78eb302eabac7b6ae04a8eadf8b2819e992d2913cdafe1a86222148982351ec
SIZE (tarantool-1.3.5-src.tar.gz) = 829327

View file

@ -0,0 +1,11 @@
--- CMakeLists.txt.orig 2011-11-25 15:33:08.997444924 +0000
+++ CMakeLists.txt 2011-11-25 15:33:30.428593855 +0000
@@ -197,7 +197,7 @@
add_subdirectory(test)
install (FILES README LICENSE doc/silverbox-protocol.txt
- DESTINATION doc)
+ DESTINATION %%DOCSDIR%%)
include (cmake/tarantool_cpack.cmake)
#

View file

@ -0,0 +1,9 @@
--- test/CMakeLists.txt.orig 2011-12-11 16:16:40.594230551 +0000
+++ test/CMakeLists.txt 2011-12-11 16:19:44.915010706 +0000
@@ -9,5 +9,4 @@
install (PROGRAMS tarantool DESTINATION bin)
install (DIRECTORY lib DESTINATION bin)
-install (FILES box/tarantool.cfg box/00000000000000000001.snap
- DESTINATION bin)
+install (FILES box/tarantool.cfg DESTINATION %%ETCDIR%%)

View file

@ -0,0 +1,17 @@
--- test/box/tarantool.cfg.orig 2011-12-13 01:02:02.069760259 +0000
+++ test/box/tarantool.cfg 2011-12-13 01:03:52.550055101 +0000
@@ -1,11 +1,11 @@
slab_alloc_arena = 0.1
-pid_file = "box.pid"
-
+pid_file = "/var/run/tarantool.pid"
+work_dir = "/var/db/tarantool"
# Use -a not -a to work correctly on FreeBSD
#
-logger="tee -a tarantool.log"
+logger="cat >> /var/log/tarantool.log"
primary_port = 33013
secondary_port = 33014

View file

@ -0,0 +1,57 @@
--- test/lib/sql.g.orig 2011-05-14 12:16:32.000000000 +0000
+++ test/lib/sql.g 2011-12-13 00:41:37.729004939 +0000
@@ -5,10 +5,6 @@
%%
-# The grammar below solely covers the functionality provided by
-# Tarantool binary protocol, from which follow all the
-# limitations. For reference please see doc/box-protocol.txt.
-
parser sql:
ignore: '\\s+'
@@ -25,8 +21,6 @@
token WHERE: 'where'
token VALUES: 'values'
token SET: 'set'
- token OR: 'or'
- token LIMIT: 'limit'
token END: '\\s*$'
rule sql: (insert {{ stmt = insert }} |
@@ -37,27 +31,19 @@
rule insert: INSERT [INTO] ident VALUES value_list
{{ return sql_ast.StatementInsert(ident, value_list) }}
- rule update: UPDATE ident SET update_list opt_simple_where
- {{ return sql_ast.StatementUpdate(ident, update_list, opt_simple_where) }}
- rule delete: DELETE FROM ident opt_simple_where
- {{ return sql_ast.StatementDelete(ident, opt_simple_where) }}
- rule select: SELECT '\*' FROM ident opt_where opt_limit
- {{ return sql_ast.StatementSelect(ident, opt_where, opt_limit) }}
+ rule update: UPDATE ident SET update_list opt_where
+ {{ return sql_ast.StatementUpdate(ident, update_list, opt_where) }}
+ rule delete: DELETE FROM ident opt_where
+ {{ return sql_ast.StatementDelete(ident, opt_where) }}
+ rule select: SELECT '\*' FROM ident opt_where
+ {{ return sql_ast.StatementSelect(ident, opt_where) }}
rule ping: PING
{{ return sql_ast.StatementPing() }}
rule predicate: ident '=' constant
{{ return (ident, constant) }}
- rule opt_simple_where: {{ return None }}
+ rule opt_where: {{ return None }}
| WHERE predicate
{{ return predicate }}
- rule opt_where: {{ return None }}
- | WHERE disjunction
- {{ return disjunction }}
- rule disjunction: predicate {{ disjunction = [predicate] }}
- [(OR predicate {{ disjunction.append(predicate) }})+]
- {{ return disjunction }}
- rule opt_limit: {{ return 0xffffffff }}
- | LIMIT NUM {{ return int(NUM) }}
rule value_list: '\(' expr {{ value_list = [expr] }}
[("," expr {{ value_list.append(expr) }} )+]
'\)' {{ return value_list }}

View file

@ -0,0 +1,92 @@
--- test/lib/sql.py.orig 2011-05-14 12:16:32.000000000 +0000
+++ test/lib/sql.py 2011-12-13 00:23:04.673107891 +0000
@@ -30,8 +30,6 @@
('WHERE', re.compile('where')),
('VALUES', re.compile('values')),
('SET', re.compile('set')),
- ('OR', re.compile('or')),
- ('LIMIT', re.compile('limit')),
('END', re.compile('\\s*$')),
]
def __init__(self, str,*args,**kw):
@@ -76,16 +74,16 @@
ident = self.ident(_context)
SET = self._scan('SET', context=_context)
update_list = self.update_list(_context)
- opt_simple_where = self.opt_simple_where(_context)
- return sql_ast.StatementUpdate(ident, update_list, opt_simple_where)
+ opt_where = self.opt_where(_context)
+ return sql_ast.StatementUpdate(ident, update_list, opt_where)
def delete(self, _parent=None):
_context = self.Context(_parent, self._scanner, 'delete', [])
DELETE = self._scan('DELETE', context=_context)
FROM = self._scan('FROM', context=_context)
ident = self.ident(_context)
- opt_simple_where = self.opt_simple_where(_context)
- return sql_ast.StatementDelete(ident, opt_simple_where)
+ opt_where = self.opt_where(_context)
+ return sql_ast.StatementDelete(ident, opt_where)
def select(self, _parent=None):
_context = self.Context(_parent, self._scanner, 'select', [])
@@ -94,8 +92,7 @@
FROM = self._scan('FROM', context=_context)
ident = self.ident(_context)
opt_where = self.opt_where(_context)
- opt_limit = self.opt_limit(_context)
- return sql_ast.StatementSelect(ident, opt_where, opt_limit)
+ return sql_ast.StatementSelect(ident, opt_where)
def ping(self, _parent=None):
_context = self.Context(_parent, self._scanner, 'ping', [])
@@ -109,8 +106,8 @@
constant = self.constant(_context)
return (ident, constant)
- def opt_simple_where(self, _parent=None):
- _context = self.Context(_parent, self._scanner, 'opt_simple_where', [])
+ def opt_where(self, _parent=None):
+ _context = self.Context(_parent, self._scanner, 'opt_where', [])
_token = self._peek('WHERE', 'END', context=_context)
if _token == 'END':
return None
@@ -119,38 +116,6 @@
predicate = self.predicate(_context)
return predicate
- def opt_where(self, _parent=None):
- _context = self.Context(_parent, self._scanner, 'opt_where', [])
- _token = self._peek('WHERE', 'LIMIT', 'END', context=_context)
- if _token != 'WHERE':
- return None
- else: # == 'WHERE'
- WHERE = self._scan('WHERE', context=_context)
- disjunction = self.disjunction(_context)
- return disjunction
-
- def disjunction(self, _parent=None):
- _context = self.Context(_parent, self._scanner, 'disjunction', [])
- predicate = self.predicate(_context)
- disjunction = [predicate]
- if self._peek('OR', 'LIMIT', 'END', context=_context) == 'OR':
- while 1:
- OR = self._scan('OR', context=_context)
- predicate = self.predicate(_context)
- disjunction.append(predicate)
- if self._peek('OR', 'LIMIT', 'END', context=_context) != 'OR': break
- return disjunction
-
- def opt_limit(self, _parent=None):
- _context = self.Context(_parent, self._scanner, 'opt_limit', [])
- _token = self._peek('LIMIT', 'END', context=_context)
- if _token == 'END':
- return 0xffffffff
- else: # == 'LIMIT'
- LIMIT = self._scan('LIMIT', context=_context)
- NUM = self._scan('NUM', context=_context)
- return int(NUM)
-
def value_list(self, _parent=None):
_context = self.Context(_parent, self._scanner, 'value_list', [])
self._scan("'\\('", context=_context)

View file

@ -0,0 +1,45 @@
--- test/lib/sql_ast.py.orig 2011-05-14 12:16:32.000000000 +0000
+++ test/lib/sql_ast.py 2011-12-13 00:23:04.673107891 +0000
@@ -242,22 +242,16 @@
class StatementSelect(StatementPing):
reqeust_type = SELECT_REQUEST_TYPE
- def __init__(self, table_name, where, limit):
+ def __init__(self, table_name, where):
self.namespace_no = table_name
- self.index_no = None
- self.key_list = []
- if not where:
- self.index_no = 0
- self.key_list = ["",]
+ if where:
+ (self.index_no, key) = where
+ self.key = [key]
else:
- for (index_no, key) in where:
- self.key_list.append(key)
- if self.index_no == None:
- self.index_no = index_no
- elif self.index_no != index_no:
- raise RuntimeError("All key values in a disjunction must refer to the same index")
+ self.index_no = 0
+ self.key = [""]
self.offset = 0
- self.limit = limit
+ self.limit = 0xffffffff
def pack(self):
buf = ctypes.create_string_buffer(PACKET_BUF_LEN)
@@ -266,10 +260,8 @@
self.index_no,
self.offset,
self.limit,
- len(self.key_list))
- offset = SELECT_REQUEST_FIXED_LEN
- for key in self.key_list:
- (buf, offset) = pack_tuple([key], buf, offset)
+ 1)
+ (buf, offset) = pack_tuple(self.key, buf, SELECT_REQUEST_FIXED_LEN)
return buf[:offset]

View file

@ -0,0 +1,158 @@
--- test/lib/tarantool_connection.py.orig 1970-01-01 00:00:00.000000000 +0000
+++ test/lib/tarantool_connection.py 2011-12-13 00:23:04.673107891 +0000
@@ -0,0 +1,155 @@
+__author__ = "Konstantin Osipov <kostja.osipov@gmail.com>"
+
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+
+import socket
+import sys
+import string
+import cStringIO
+import yaml
+import re
+import sql
+import struct
+import errno
+
+is_admin_re = re.compile("^\s*(show|save|exec|exit|reload|help)", re.I)
+
+class AdminConnection:
+ def __init__(self, host, port):
+ self.host = host
+ self.port = port
+ self.is_connected = False
+ self.stream = cStringIO.StringIO()
+
+ def connect(self):
+ self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ self.socket.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1)
+ self.socket.connect((self.host, self.port))
+ self.is_connected = True
+
+ def disconnect(self):
+ if self.is_connected:
+ self.socket.close()
+ self.is_connected = False
+
+ def reconnect(self):
+ self.disconnect()
+ self.connect()
+
+ def opt_reconnect(self):
+ """ On a socket which was disconnected, recv of 0 bytes immediately
+ returns with no data. On a socket which is alive, it returns EAGAIN.
+ Make use of this property and detect whether or not the socket is
+ dead. Reconnect a dead socket, do nothing if the socket is good."""
+ try:
+ if self.socket.recv(0, socket.MSG_DONTWAIT) == '':
+ self.reconnect()
+ except socket.error as e:
+ if e.errno == errno.EAGAIN:
+ pass
+ else:
+ self.reconnect()
+
+ def execute(self, command):
+ self.opt_reconnect()
+ return self.execute_no_reconnect(command)
+
+ def execute_no_reconnect(self, command):
+ self.socket.sendall(command)
+
+ bufsiz = 4096
+ res = ""
+
+ while True:
+ buf = self.socket.recv(bufsiz)
+ if not buf:
+ break
+ res = res + buf;
+ if (res.rfind("\r\n...\r\n") >= 0):
+ break
+
+ # validate yaml by parsing it
+ yaml.load(res)
+
+ return res
+
+ def write(self, fragment):
+ """This is to support print >> admin, "command" syntax.
+ For every print statement, write is invoked twice: one to
+ write the command itself, and another to write \n. We should
+ accumulate all writes until we receive \n. When we receive it,
+ we execute the command, and rewind the stream."""
+
+ newline_pos = fragment.rfind("\n")
+ while newline_pos >= 0:
+ self.stream.write(fragment[:newline_pos+1])
+ statement = self.stream.getvalue()
+ sys.stdout.write(statement)
+ sys.stdout.write(self.execute(statement))
+ fragment = fragment[newline_pos+1:]
+ newline_pos = fragment.rfind("\n")
+ self.stream.seek(0)
+ self.stream.truncate()
+
+ self.stream.write(fragment)
+
+ def __enter__(self):
+ self.connect()
+ return self
+
+ def __exit__(self, type, value, tb):
+ self.disconnect()
+
+class DataConnection(AdminConnection):
+
+ def recvall(self, length):
+ res = ""
+ while len(res) < length:
+ buf = self.socket.recv(length - len(res))
+ res = res + buf
+ return res
+
+ def execute_no_reconnect(self, command):
+ statement = sql.parse("sql", command)
+ if statement == None:
+ return "You have an error in your SQL syntax\n"
+
+ payload = statement.pack()
+ header = struct.pack("<lll", statement.reqeust_type, len(payload), 0)
+
+ self.socket.sendall(header)
+ if len(payload):
+ self.socket.sendall(payload)
+
+ IPROTO_HEADER_SIZE = 12
+
+ header = self.recvall(IPROTO_HEADER_SIZE)
+
+ response_len = struct.unpack("<lll", header)[1]
+
+ if response_len:
+ response = self.recvall(response_len)
+ else:
+ response = None
+
+ return statement.unpack(response) + "\n"
+

View file

@ -0,0 +1,266 @@
--- test/lib/tarantool_silverbox_server.py.orig 2011-05-14 12:16:32.000000000 +0000
+++ test/lib/tarantool_silverbox_server.py 2011-12-13 00:23:04.673107891 +0000
@@ -1,35 +1,234 @@
+import os
+import stat
import shutil
import subprocess
-import yaml
-import ConfigParser
-from tarantool_server import TarantoolServer, TarantoolConfigFile
-from tarantool_admin import TarantoolAdmin
-from silverbox import Silverbox
-
-class TarantoolSilverboxServer(TarantoolServer):
- def __new__(cls, core="tarantool", module="silverbox"):
- return TarantoolServer.__new__(cls)
-
- def __init__(self, core="tarantool", module="silverbox"):
- TarantoolServer.__init__(self, core, module)
-
- def configure(self, config):
- TarantoolServer.configure(self, config)
- with open(self.config) as fp:
- dummy_section_name = "tarantool"
- config = ConfigParser.ConfigParser()
- config.readfp(TarantoolConfigFile(fp, dummy_section_name))
- self.primary_port = int(config.get(dummy_section_name, "primary_port"))
- self.admin_port = int(config.get(dummy_section_name, "admin_port"))
- self.port = self.admin_port
- self.admin = TarantoolAdmin("localhost", self.admin_port)
- self.sql = Silverbox("localhost", self.primary_port)
-
- def init(self):
-# init storage
- subprocess.check_call([self.binary, "--init_storage"],
- cwd = self.vardir,
+import pexpect
+import sys
+import signal
+import time
+import socket
+import daemon
+import glob
+
+def wait_until_connected(host, port):
+ """Wait until the server is started and accepting connections"""
+ is_connected = False
+ while not is_connected:
+ try:
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ sock.connect((host, port))
+ is_connected = True
+ sock.close()
+ except socket.error as e:
+ time.sleep(0.001)
+
+
+def prepare_gdb(args):
+ """Prepare server startup arguments to run under gdb."""
+ if "TERM" in os.environ:
+ term = os.environ["TERM"]
+ else:
+ term = "xterm"
+
+ if term not in ["xterm", "rxvt", "urxvt", "gnome-terminal", "konsole"]:
+ raise RuntimeError("--gdb: unsupported terminal {0}".format(term))
+
+ args = [ term, "-e ", "gdb", "-ex", "break main", "-ex", "run"] + args
+ return args
+
+
+def prepare_valgrind(args, valgrind_opts):
+ "Prepare server startup arguments to run under valgrind."
+ args = ([ "valgrind", "--log-file=valgrind.log", "--quiet" ] +
+ valgrind_opts.split(" ") + args)
+ return args
+
+
+def check_tmpfs_exists():
+ return os.uname()[0] in 'Linux' and os.path.isdir("/dev/shm")
+
+def create_tmpfs_vardir(vardir):
+ os.mkdir(os.path.join("/dev/shm", vardir))
+ os.symlink(os.path.join("/dev/shm", vardir), vardir)
+
+class TarantoolSilverboxServer:
+ """Server represents a single server instance. Normally, the
+ program operates with only one server, but in future we may add
+ replication slaves. The server is started once at the beginning
+ of each suite, and stopped at the end."""
+
+ def __init__(self, args, suite_ini):
+ """Set server options: path to configuration file, pid file, exe, etc."""
+ self.args = args
+ self.suite_ini = suite_ini
+ self.path_to_pidfile = os.path.join(args.vardir, suite_ini["pidfile"])
+ self.path_to_exe = None
+ self.abspath_to_exe = None
+ self.is_started = False
+
+ def install(self, silent = False):
+ """Start server instance: check if the old one exists, kill it
+ if necessary, create necessary directories and files, start
+ the server. The server working directory is taken from 'vardir',
+ specified in the prgoram options.
+ Currently this is implemented for tarantool_silverbox only."""
+
+ vardir = self.args.vardir
+
+ if not silent:
+ print "Installing the server..."
+
+ if self.path_to_exe == None:
+ self.path_to_exe = self.find_exe()
+ self.abspath_to_exe = os.path.abspath(self.path_to_exe)
+
+ if not silent:
+ print " Found executable at " + self.path_to_exe + "."
+
+ if not silent:
+ print " Creating and populating working directory in " +\
+ vardir + "..."
+
+ if os.access(vardir, os.F_OK):
+ if not silent:
+ print " Found old vardir, deleting..."
+ self.kill_old_server()
+ for filename in (glob.glob(os.path.join(vardir, "*.snap")) +
+ glob.glob(os.path.join(vardir, "*.inprogress")) +
+ glob.glob(os.path.join(vardir, "*.xlog")) +
+ glob.glob(os.path.join(vardir, "*.cfg")) +
+ glob.glob(os.path.join(vardir, "*.log")) +
+ glob.glob(os.path.join(vardir, "*.core.*")) +
+ glob.glob(os.path.join(vardir, "core"))):
+ os.remove(filename)
+ else:
+ if (self.args.mem == True and check_tmpfs_exists() and
+ os.path.basename(vardir) == vardir):
+ create_tmpfs_vardir(vardir)
+ else:
+ os.mkdir(vardir)
+
+ shutil.copy(self.suite_ini["config"], self.args.vardir)
+
+ subprocess.check_call([self.abspath_to_exe, "--init_storage"],
+ cwd = self.args.vardir,
# catch stdout/stderr to not clutter output
stdout = subprocess.PIPE,
stderr = subprocess.PIPE)
+ p = subprocess.Popen([self.abspath_to_exe, "--version"],
+ cwd = self.args.vardir,
+ stdout = subprocess.PIPE)
+ version = p.stdout.read().rstrip()
+ p.wait()
+
+ if not silent:
+ print "Starting {0} {1}.".format(os.path.basename(self.abspath_to_exe),
+ version)
+
+ def start(self, silent = False):
+
+ if self.is_started:
+ if not silent:
+ print "The server is already started."
+ return
+
+ if not silent:
+ print "Starting the server..."
+
+ args = [self.abspath_to_exe]
+
+ if (self.args.start_and_exit and
+ not self.args.valgrind and not self.args.gdb):
+ args.append("--daemonize")
+ if self.args.gdb:
+ args = prepare_gdb(args)
+ elif self.args.valgrind:
+ args = prepare_valgrind(args, self.args.valgrind_opts)
+
+ if self.args.start_and_exit and self.args.valgrind:
+ pid = os.fork()
+ if pid > 0:
+ os.wait()
+ else:
+ with daemon.DaemonContext(working_directory = self.args.vardir):
+ os.execvp(args[0], args)
+ else:
+ self.server = pexpect.spawn(args[0], args[1:], cwd = self.args.vardir)
+ if self.args.start_and_exit:
+ self.server.wait()
+
+# wait until the server is connectedk
+ if self.args.gdb and self.args.start_and_exit:
+ time.sleep(1)
+ elif not self.args.start_and_exit and not self.args.gdb:
+ self.server.expect_exact("entering event loop")
+ else:
+ wait_until_connected(self.suite_ini["host"], self.suite_ini["port"])
+
+# Set is_started flag, to nicely support cleanup during an exception.
+ self.is_started = True
+
+
+ def stop(self, silent = False):
+ """Stop server instance. Do nothing if the server is not started,
+ to properly shut down the server in case of an exception during
+ start up."""
+ if self.is_started:
+ if not silent:
+ print "Stopping the server..."
+ if self.args.gdb:
+ self.kill_old_server(True)
+ self.server.terminate()
+ self.server.expect(pexpect.EOF)
+ self.is_started = False
+ elif not silent:
+ print "The server is not started."
+
+ def restart(self):
+ self.stop(True)
+ self.start(True)
+
+ def test_option(self, option_list_str):
+ args = [self.abspath_to_exe] + option_list_str.split()
+ print " ".join([os.path.basename(self.abspath_to_exe)] + args[1:])
+ output = subprocess.Popen(args,
+ cwd = self.args.vardir,
+ stdout = subprocess.PIPE,
+ stderr = subprocess.STDOUT).stdout.read()
+ print output
+
+
+ def find_exe(self):
+ """Locate server executable in the bindir. We just take
+ the first thing looking like an exe in there."""
+
+ print " Looking for server binary in {0}...".format(self.args.bindir)
+ if (os.access(self.args.bindir, os.F_OK) == False or
+ stat.S_ISDIR(os.stat(self.args.bindir).st_mode) == False):
+ raise RuntimeError("Directory " + self.args.bindir + " doesn't exist")
+
+ for f in os.listdir(self.args.bindir):
+ f = os.path.join(self.args.bindir, f)
+ st_mode = os.stat(f).st_mode
+ if stat.S_ISREG(st_mode) and st_mode & stat.S_IXUSR:
+ return f
+
+ raise RuntimeError("Can't find server executable in " + self.args.bindir)
+
+ def kill_old_server(self, silent = False):
+ """Kill old server instance if it exists."""
+ if os.access(self.path_to_pidfile, os.F_OK) == False:
+ return # Nothing to do
+ pid = 0
+ with open(self.path_to_pidfile) as f:
+ pid = int(f.read())
+ if not silent:
+ print " Found old server, pid {0}, killing...".format(pid)
+ try:
+ os.kill(pid, signal.SIGTERM)
+ while os.kill(pid, 0) != -1:
+ time.sleep(0.001)
+ except OSError:
+ pass
+

View file

@ -0,0 +1,210 @@
--- test/lib/test_suite.py.orig 2011-05-14 12:16:32.000000000 +0000
+++ test/lib/test_suite.py 2011-12-13 00:23:04.673107891 +0000
@@ -10,13 +10,21 @@
import filecmp
import shlex
import time
-from server import Server
+from tarantool_silverbox_server import TarantoolSilverboxServer
+from tarantool_connection import AdminConnection, DataConnection
import tarantool_preprocessor
import re
import cStringIO
import string
import traceback
+class TestRunException(RuntimeError):
+ """A common exception to use across the program."""
+ def __init__(self, message):
+ self.message = message
+ def __str__(self):
+ return self.message
+
class FilteredStream:
"""Helper class to filter .result file output"""
def __init__(self, filename):
@@ -66,12 +74,12 @@
"""Initialize test properties: path to test file, path to
temporary result file, path to the client program, test status."""
self.name = name
- self.args = args
- self.suite_ini = suite_ini
self.result = name.replace(".test", ".result")
- self.tmp_result = os.path.join(self.args.vardir,
+ self.tmp_result = os.path.join(suite_ini["vardir"],
os.path.basename(self.result))
self.reject = name.replace(".test", ".reject")
+ self.args = args
+ self.suite_ini = suite_ini
self.is_executed = False
self.is_executed_ok = None
self.is_equal_result = None
@@ -81,7 +89,7 @@
"""Return true if this test was run successfully."""
return self.is_executed and self.is_executed_ok and self.is_equal_result
- def run(self, server):
+ def run(self):
"""Execute the test assuming it's a python program.
If the test aborts, print its output to stdout, and raise
an exception. Else, comprare result and reject files.
@@ -91,10 +99,18 @@
diagnostics = "unknown"
save_stdout = sys.stdout
+ admin = AdminConnection(self.suite_ini["host"],
+ self.suite_ini["admin_port"])
+ sql = DataConnection(self.suite_ini["host"],
+ self.suite_ini["port"])
+ server = self.suite_ini["server"]
try:
+ admin.connect()
+ sql.connect()
sys.stdout = FilteredStream(self.tmp_result)
- stdout_fileno = sys.stdout.stream.fileno()
- execfile(self.name, dict(locals(), **server.__dict__))
+ server = self.suite_ini["server"]
+ vardir = self.suite_ini["vardir"]
+ execfile(self.name, globals(), locals())
self.is_executed_ok = True
except Exception as e:
traceback.print_exc(e)
@@ -103,6 +119,8 @@
if sys.stdout and sys.stdout != save_stdout:
sys.stdout.close()
sys.stdout = save_stdout;
+ admin.disconnect()
+ sql.disconnect()
self.is_executed = True
@@ -111,7 +129,7 @@
if self.args.valgrind:
self.is_valgrind_clean = \
- check_valgrind_log(server.valgrind_log) == False
+ check_valgrind_log(self.suite_ini["valgrind_log"]) == False
if self.is_executed_ok and self.is_equal_result and self.is_valgrind_clean:
print "[ pass ]"
@@ -133,12 +151,12 @@
where = ": wrong test output"
elif not self.is_valgrind_clean:
os.remove(self.reject)
- self.print_diagnostics(server.valgrind_log,
+ self.print_diagnostics(self.suite_ini["valgrind_log"],
"Test failed! Last 10 lines of valgrind.log:")
where = ": there were warnings in valgrind.log"
- if not self.args.is_force:
- raise RuntimeError("Failed to run test " + self.name + where)
+ if not self.suite_ini["is_force"]:
+ raise TestRunException("Failed to run test " + self.name + where)
def print_diagnostics(self, logfile, message):
@@ -167,6 +185,20 @@
for line in diff:
sys.stdout.write(line)
+class TarantoolConfigFile:
+ """ConfigParser can't read files without sections, work it around"""
+ def __init__(self, fp, section_name):
+ self.fp = fp
+ self.section_name = "[" + section_name + "]"
+ def readline(self):
+ if self.section_name:
+ section_name = self.section_name
+ self.section_name = None
+ return section_name
+ # tarantool.cfg puts string values in quote
+ return self.fp.readline().replace("\"", '')
+
+
class TestSuite:
"""Each test suite contains a number of related tests files,
located in the same directory on disk. Each test file has
@@ -186,13 +218,15 @@
self.args = args
self.tests = []
self.ini = {}
-
- self.ini["core"] = "tarantool"
- self.ini["module"] = "silverbox"
+ self.ini["suite_path"] = suite_path
+ self.ini["host"] = "localhost"
+ self.ini["is_force"] = self.args.is_force
+ self.ini["vardir"] = args.vardir
+ self.ini["valgrind_log"] = os.path.join(args.vardir, "valgrind.log")
if os.access(suite_path, os.F_OK) == False:
- raise RuntimeError("Suite \"" + suite_path +\
- "\" doesn't exist")
+ raise TestRunException("Suite \"" + suite_path +\
+ "\" doesn't exist")
# read the suite config
config = ConfigParser.ConfigParser()
@@ -203,6 +237,16 @@
self.ini["disabled"] = dict.fromkeys(self.ini["disabled"].split(" "))
else:
self.ini["disabled"] = dict()
+# import the necessary module for test suite client
+
+# now read the server config, we need some properties from it
+
+ with open(self.ini["config"]) as fp:
+ dummy_section_name = "tarantool_silverbox"
+ config.readfp(TarantoolConfigFile(fp, dummy_section_name))
+ self.ini["pidfile"] = config.get(dummy_section_name, "pid_file")
+ self.ini["admin_port"] = int(config.get(dummy_section_name, "admin_port"))
+ self.ini["port"] = int(config.get(dummy_section_name, "primary_port"))
print "Collecting tests in \"" + suite_path + "\": " +\
self.ini["description"] + "."
@@ -216,17 +260,9 @@
def run_all(self):
"""For each file in the test suite, run client program
assuming each file represents an individual test."""
- try:
- server = Server(self.ini["core"], self.ini["module"])
- except Exception as e:
- print e
- raise RuntimeError("Unknown server: core = {0}, module = {1}".format(
- self.ini["core"], self.ini["module"]))
- server.deploy(self.ini["config"],
- server.find_exe(self.args.builddir, silent=False),
- self.args.vardir,
- self.args.mem, self.args.start_and_exit, self.args.gdb, self.args.valgrind,
- silent=False)
+ server = TarantoolSilverboxServer(self.args, self.ini)
+ server.install()
+ server.start()
if self.args.start_and_exit:
print " Start and exit requested, exiting..."
exit(0)
@@ -247,7 +283,7 @@
if os.path.basename(test.name) in self.ini["disabled"]:
print "[ skip ]"
else:
- test.run(server)
+ test.run()
if not test.passed():
failed_tests.append(test.name)
@@ -255,11 +291,9 @@
if len(failed_tests):
print "Failed {0} tests: {1}.".format(len(failed_tests),
", ".join(failed_tests))
- server.stop(silent=False)
- server.cleanup()
+ server.stop();
- if self.args.valgrind and check_valgrind_log(server.valgrind_log):
+ if self.args.valgrind and check_valgrind_log(self.ini["valgrind_log"]):
print " Error! There were warnings/errors in valgrind log file:"
- print_tail_n(server.valgrind_log, 20)
- return 1
- return len(failed_tests)
+ print_tail_n(self.ini["valgrind_log"], 20)
+

View file

@ -0,0 +1,30 @@
--- test/tarantool.orig 2011-05-14 12:16:32.000000000 +0000
+++ test/tarantool 2011-12-13 01:12:12.696699437 +0000
@@ -1,4 +1,4 @@
-#! /usr/bin/python
+#! /usr/bin/env python
"""A simplistic client for tarantool/silverbox: administrative
console and SQL client.
@@ -32,8 +32,8 @@
import socket
import sys
import string
-from lib.tarantool_admin import TarantoolAdmin, is_admin_re
-from lib.box import Box
+from lib.tarantool_connection import AdminConnection, DataConnection, \
+ is_admin_re
class Options:
def __init__(self):
@@ -94,8 +94,8 @@
def main():
init_readline_history()
options = Options()
- admin_con = TarantoolAdmin(options.args.host, options.args.admin_port)
- data_con = Box(options.args.host, options.args.port)
+ admin_con = AdminConnection(options.args.host, options.args.admin_port)
+ data_con = DataConnection(options.args.host, options.args.port)
try:
admin_con.connect()
data_con.connect()

View file

@ -0,0 +1,10 @@
#########################################################
#
#
# After install you'll need init storage:
#
# %%PREFIX%%/bin/tarantool_silverbox --init-storage \
# -c %%ETCDIR%%/tarantool.cfg
#
#
#########################################################

View file

@ -0,0 +1,29 @@
#!/bin/sh
#
# $FreeBSD$
# PROVIDE: tarantool
# REQUIRE: LOGIN
# KEYWORD: shutdown
#
# tarantool_enable="YES"
# tarantool_config=""
#
. /etc/rc.subr
name="tarantool"
tarantool_enable=${tarantool_enable:-"NO"}
tarantool_config=${tarantool_config:-"%%ETCDIR%%/$name.cfg"}
rcvar=`set_rcvar`
load_rc_config "$name"
command="%%PREFIX%%/bin/tarantool_silverbox"
command_args="--daemonize --config ${tarantool_config}"
pidfile="/var/run/$name.pid"
run_rc_command "$1"

View file

@ -0,0 +1,5 @@
Tarantool/Box, or simply Tarantool, is a high performance key/value
storage server. The code is available for free under the terms of
BSD license. Supported platforms are GNU/Linux and FreeBSD.
WWW: http://tarantool.org/

View file

@ -0,0 +1,21 @@
bin/lib/__init__.py
bin/lib/sql.g
bin/lib/sql.py
bin/lib/sql_ast.py
bin/lib/tarantool_connection.py
bin/lib/tarantool_preprocessor.py
bin/lib/tarantool_silverbox_server.py
bin/lib/test_suite.py
bin/lib/yapps/__init__.py
bin/lib/yapps/runtime.py
bin/tarantool
bin/tarantool_feeder
bin/tarantool_silverbox
%%DOCSDIR%%/LICENSE
%%DOCSDIR%%/README
%%DOCSDIR%%/silverbox-protocol.txt
%%ETCDIR%%/tarantool.cfg
@dirrm %%DOCSDIR%%
@dirrm %%ETCDIR%%
@dirrm bin/lib/yapps
@dirrm bin/lib