From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from localhost (localhost [127.0.0.1]) by turing.freelists.org (Avenir Technologies Mail Multiplex) with ESMTP id AEC512849B for ; Wed, 28 Mar 2018 09:35:54 -0400 (EDT) Received: from turing.freelists.org ([127.0.0.1]) by localhost (turing.freelists.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id XWJle94WjGFE for ; Wed, 28 Mar 2018 09:35:54 -0400 (EDT) Received: from smtp63.i.mail.ru (smtp63.i.mail.ru [217.69.128.43]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by turing.freelists.org (Avenir Technologies Mail Multiplex) with ESMTPS id DABA228468 for ; Wed, 28 Mar 2018 09:35:53 -0400 (EDT) From: Vladislav Shpilevoy Subject: [tarantool-patches] [PATCH v2 2/3] console: do not use netbox for console text connections Date: Wed, 28 Mar 2018 16:35:49 +0300 Message-Id: <7bbc3f973856120f5394f5193d5f02b538606195.1522243429.git.v.shpilevoy@tarantool.org> In-Reply-To: References: In-Reply-To: References: Sender: tarantool-patches-bounce@freelists.org Errors-to: tarantool-patches-bounce@freelists.org Reply-To: tarantool-patches@freelists.org List-help: List-unsubscribe: List-software: Ecartis version 1.0.0 List-Id: tarantool-patches List-subscribe: List-owner: List-post: List-archive: To: tarantool-patches@freelists.org Cc: Vladislav Shpilevoy Netbox console support complicates both netbox and console. Lets use sockets directly for text protocol. Part of #2677 --- src/box/lua/console.lua | 162 +++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 131 insertions(+), 31 deletions(-) diff --git a/src/box/lua/console.lua b/src/box/lua/console.lua index d49cf42be..b853d523c 100644 --- a/src/box/lua/console.lua +++ b/src/box/lua/console.lua @@ -8,6 +8,9 @@ local log = require('log') local errno = require('errno') local urilib = require('uri') local yaml = require('yaml') +local net_box = require('net.box') + +local YAML_TERM = '\n...\n' -- admin formatter must be able to encode any Lua variable local formatter = yaml.new() @@ -76,26 +79,117 @@ local function eval(line) return local_eval(nil, line) end +local text_connection_mt = { + __index = { + -- + -- Close underlying socket. + -- + close = function(self) + self._socket:close() + end, + -- + -- Write a text into a socket. + -- @param test Text to send. + -- @retval not nil Bytes sent. + -- @retval nil Error. + -- + write = function(self, text) + -- It is the hack to protect from SIGPIPE, which is + -- not ignored under debugger (gdb, lldb) on send in + -- a socket, that is actually closed. If a socket is + -- readable and read() returns nothing then the socket + -- is closed, and writing into it will raise SIGPIPE. + if self._socket:readable(0) then + local rc = self._socket:read({chunk = 1}) + if not rc or rc == '' then + return nil + else + assert(#rc == 1) + -- Make the char be unread. + self._socket.rbuf.wpos = self._socket.rbuf.wpos - 1 + end + end + return self._socket:write(text) + end, + -- + -- Read a text from a socket until YAML terminator. + -- @retval not nil Well formatted YAML. + -- @retval nil Error. + -- + read = function(self) + local ret = self._socket:read(YAML_TERM) + if ret and ret ~= '' then + return ret + end + end, + -- + -- Write + Read. + -- + eval = function(self, text) + text = text..'$EOF$\n' + if self:write(text) then + local rc = self:read() + if rc then + return rc + end + end + error(self:set_error()) + end, + -- + -- Make the connection be in error state, set error + -- message. + -- @retval Error message. + -- + set_error = function(self) + self.state = 'error' + self.error = self._socket:error() + if not self.error then + self.error = 'Peer closed' + end + return self.error + end, + } +} + +-- +-- Wrap an existing socket inside a netbox-like object. +-- @param connection Socket to wrap. +-- @param url Parsed destination URL. +-- @retval nil, err Error, and err contains an error message. +-- @retval not nil Netbox-like object. +-- +local function wrap_text_socket(connection, url) + local conn = setmetatable({ + _socket = connection, + state = 'active', + host = url.host or 'localhost', + port = url.service, + }, text_connection_mt) + if not conn:write('require("console").delimiter("$EOF$")\n') or + not conn:read() then + conn:set_error() + end + return conn +end + -- -- Evaluate command on remote instance -- local function remote_eval(self, line) - if not line or self.remote.state ~= 'active' then - local err = self.remote.error - self.remote:close() - self.remote = nil - -- restore local REPL mode - self.eval = nil - self.prompt = nil - self.completion = nil - pcall(self.on_client_disconnect, self) - return (err and format(false, err)) or '' + if line and self.remote.state == 'active' then + local ok, res = pcall(self.remote.eval, self.remote, line) + if self.remote.state == 'active' then + return ok and res or format(false, res) + end end - -- - -- execute line - -- - local ok, res = pcall(self.remote.eval, self.remote, line) - return ok and res or format(false, res) + local err = self.remote.error + self.remote:close() + self.remote = nil + self.eval = nil + self.prompt = nil + self.completion = nil + pcall(self.on_client_disconnect, self) + return (err and format(false, err)) or '' end -- @@ -285,12 +379,7 @@ end -- -- Connect to remove instance -- -local netbox_connect local function connect(uri, opts) - if not netbox_connect then -- workaround the broken loader - netbox_connect = require('net.box').connect - end - opts = opts or {} local self = fiber.self().storage.console @@ -306,18 +395,29 @@ local function connect(uri, opts) error('Usage: console.connect("[login:password@][host:]port")') end - -- connect to remote host + local connection, greeting = + net_box.establish_connection(u.host, u.service, opts.timeout) + if not connection then + log.verbose(greeting) + box.error(box.error.NO_CONNECTION) + end local remote - remote = netbox_connect(u.host, u.service, { - user = u.login, password = u.password, - console = true, connect_timeout = opts.timeout - }) - remote.host, remote.port = u.host or 'localhost', u.service - - -- run disconnect trigger if connection failed - if not remote:is_connected() then - pcall(self.on_client_disconnect, self) - error('Connection is not established: '..remote.error) + if greeting.protocol == 'Lua console' then + remote = wrap_text_socket(connection, u) + else + opts = { + connect_timeout = opts.timeout, + user = u.login, + password = u.password, + } + remote = net_box.wrap(connection, greeting, u.host, u.service, opts) + if not remote.host then + remote.host = 'localhost' + end + local old_eval = remote.eval + remote.eval = function(con, line) + return old_eval(con, 'return require("console").eval(...)', {line}) + end end -- check connection && permissions -- 2.14.3 (Apple Git-98)