Blame SOURCES/bz1783106-01-fix-sinatra-wrapper-performance-issue.patch

bc4e95
From d54c102cee7a61dd3eccd62d60af218aa97a85fc Mon Sep 17 00:00:00 2001
bc4e95
From: Ivan Devat <idevat@redhat.com>
bc4e95
Date: Thu, 9 Jan 2020 15:53:37 +0100
bc4e95
Subject: [PATCH 6/7] squash bz1783106 fix-sinatra-wrapper-performance-issue
bc4e95
bc4e95
create prototype of tornado - thin communication
bc4e95
bc4e95
put socket path to settings
bc4e95
bc4e95
don't mix logs from threads in ruby daemon
bc4e95
bc4e95
run ruby daemon via systemd units
bc4e95
bc4e95
support trailing slash by gui urls e.g. /manage/
bc4e95
bc4e95
decode body from ruby response for log
bc4e95
bc4e95
configure ruby wrapper by socket path
bc4e95
bc4e95
remove env values not used for ruby calls any more
bc4e95
bc4e95
deal with ruby daemon communication issues
bc4e95
bc4e95
fix tests
bc4e95
bc4e95
cleanup ruby server code
bc4e95
bc4e95
deal with errors from ruby daemon in python daemon
bc4e95
bc4e95
remove unused cmdline wrapper
bc4e95
bc4e95
add ruby daemon infrastructure to spec etc.
bc4e95
bc4e95
stop logging to stderr from ruby daemon
bc4e95
bc4e95
fix spec file
bc4e95
bc4e95
* add missing cp for new rubygems
bc4e95
* make sure to start the new ruby daemon on package upgrade
bc4e95
* tests: give the new daemon enough time to start
bc4e95
---
bc4e95
 .gitlab-ci.yml                            |   7 +-
bc4e95
 Makefile                                  |   6 +
bc4e95
 pcs.spec.in                               |  30 ++++-
bc4e95
 pcs/daemon/app/sinatra_ui.py              |   2 +-
bc4e95
 pcs/daemon/env.py                         |  36 ------
bc4e95
 pcs/daemon/ruby_pcsd.py                   | 136 +++++++++++-----------
bc4e95
 pcs/daemon/run.py                         |   8 +-
bc4e95
 pcs/settings_default.py                   |   1 +
bc4e95
 pcs_test/tier0/daemon/app/fixtures_app.py |   3 +-
bc4e95
 pcs_test/tier0/daemon/test_env.py         |  66 +----------
bc4e95
 pcs_test/tier0/daemon/test_ruby_pcsd.py   |  13 +--
bc4e95
 pcsd/Gemfile                              |   1 +
bc4e95
 pcsd/Gemfile.lock                         |   7 ++
bc4e95
 pcsd/Makefile                             |   3 +
bc4e95
 pcsd/bootstrap.rb                         |  20 +++-
bc4e95
 pcsd/cfgsync.rb                           |   6 +-
bc4e95
 pcsd/pcs.rb                               |   9 +-
bc4e95
 pcsd/pcsd-cli.rb                          |   3 +-
bc4e95
 pcsd/pcsd-ruby.service                    |  20 ++++
bc4e95
 pcsd/pcsd.conf                            |   4 +
bc4e95
 pcsd/pcsd.rb                              |  31 ++---
bc4e95
 pcsd/pcsd.service                         |   2 +
bc4e95
 pcsd/pcsd.service-runner                  |  24 ++++
bc4e95
 pcsd/remote.rb                            |   6 +-
bc4e95
 pcsd/rserver.rb                           |  98 ++++++++++++++++
bc4e95
 pcsd/settings.rb                          |   1 +
bc4e95
 pcsd/settings.rb.debian                   |   1 +
bc4e95
 pcsd/sinatra_cmdline_wrapper.rb           |  63 ----------
bc4e95
 28 files changed, 330 insertions(+), 277 deletions(-)
bc4e95
 create mode 100644 pcsd/pcsd-ruby.service
bc4e95
 create mode 100644 pcsd/pcsd.service-runner
bc4e95
 create mode 100644 pcsd/rserver.rb
bc4e95
 delete mode 100644 pcsd/sinatra_cmdline_wrapper.rb
bc4e95
bc4e95
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
bc4e95
index 23ab56a9..92b32033 100644
bc4e95
--- a/.gitlab-ci.yml
bc4e95
+++ b/.gitlab-ci.yml
bc4e95
@@ -116,8 +116,11 @@ python_smoke_tests:
bc4e95
         procps-ng
bc4e95
         rpms/pcs-ci-*.rpm
bc4e95
       "
bc4e95
-    - /usr/sbin/pcsd & # start pcsd
bc4e95
-    - sleep 10 # wait for pcsd to start up properly
bc4e95
+    - export GEM_HOME=/usr/lib/pcsd/vendor/bundle/ruby
bc4e95
+    - /usr/lib/pcsd/pcsd & # start pcsd (ruby - thin)
bc4e95
+    - sleep 10 # wait for pcsd (ruby - thin) to start up properly
bc4e95
+    - /usr/sbin/pcsd & # start pcsd (python - tornado)
bc4e95
+    - sleep 10 # wait for pcsd (python - tornado) to start up properly
bc4e95
     - pcs_test/smoke.sh
bc4e95
   artifacts:
bc4e95
     paths:
bc4e95
diff --git a/Makefile b/Makefile
bc4e95
index f2b0d9b9..b9f64acd 100644
bc4e95
--- a/Makefile
bc4e95
+++ b/Makefile
bc4e95
@@ -267,7 +267,12 @@ ifeq ($(IS_DEBIAN)$(IS_SYSTEMCTL),truefalse)
bc4e95
 else
bc4e95
 	install -d ${DEST_SYSTEMD_SYSTEM}
bc4e95
 	install -m 644 ${SYSTEMD_SERVICE_FILE} ${DEST_SYSTEMD_SYSTEM}/pcsd.service
bc4e95
+	install -m 644 pcsd/pcsd-ruby.service ${DEST_SYSTEMD_SYSTEM}/pcsd-ruby.service
bc4e95
 endif
bc4e95
+	# ${DEST_LIB}/pcsd/pcsd holds the selinux context
bc4e95
+	install -m 755 pcsd/pcsd.service-runner ${DEST_LIB}/pcsd/pcsd
bc4e95
+	rm ${DEST_LIB}/pcsd/pcsd.service-runner
bc4e95
+	
bc4e95
 	install -m 700 -d ${DESTDIR}/var/lib/pcsd
bc4e95
 	install -m 644 -D pcsd/pcsd.logrotate ${DESTDIR}/etc/logrotate.d/pcsd
bc4e95
 	install -m644 -D pcsd/pcsd.8 ${DEST_MAN}/pcsd.8
bc4e95
@@ -293,6 +298,7 @@ ifeq ($(IS_DEBIAN)$(IS_SYSTEMCTL),truefalse)
bc4e95
 	rm -f ${DEST_INIT}/pcsd
bc4e95
 else
bc4e95
 	rm -f ${DEST_SYSTEMD_SYSTEM}/pcsd.service
bc4e95
+	rm -f ${DEST_SYSTEMD_SYSTEM}/pcsd-ruby.service
bc4e95
 	rm -f ${DEST_SYSTEMD_SYSTEM}/pcs_snmp_agent.service
bc4e95
 endif
bc4e95
 	rm -f ${DESTDIR}/etc/pam.d/pcsd
bc4e95
diff --git a/pcs.spec.in b/pcs.spec.in
bc4e95
index 5195dc51..32fbf614 100644
bc4e95
--- a/pcs.spec.in
bc4e95
+++ b/pcs.spec.in
bc4e95
@@ -28,7 +28,9 @@ Summary: Pacemaker Configuration System
bc4e95
 %global pyagentx_version   0.4.pcs.2
bc4e95
 %global tornado_version    6.0.3
bc4e95
 %global version_rubygem_backports  3.11.4
bc4e95
+%global version_rubygem_daemons  1.3.1
bc4e95
 %global version_rubygem_ethon  0.11.0
bc4e95
+%global version_rubygem_eventmachine  1.2.7
bc4e95
 %global version_rubygem_ffi  1.9.25
bc4e95
 %global version_rubygem_json  2.1.0
bc4e95
 %global version_rubygem_mustermann  1.0.3
bc4e95
@@ -37,6 +39,7 @@ Summary: Pacemaker Configuration System
bc4e95
 %global version_rubygem_rack_protection  2.0.4
bc4e95
 %global version_rubygem_rack_test  1.0.0
bc4e95
 %global version_rubygem_sinatra  2.0.4
bc4e95
+%global version_rubygem_thin  1.7.2
bc4e95
 %global version_rubygem_tilt  2.0.9
bc4e95
 
bc4e95
 # We do not use _libdir macro because upstream is not prepared for it.
bc4e95
@@ -83,6 +86,9 @@ Source89: https://rubygems.org/downloads/rack-protection-%{version_rubygem_rack_
bc4e95
 Source90: https://rubygems.org/downloads/rack-test-%{version_rubygem_rack_test}.gem
bc4e95
 Source91: https://rubygems.org/downloads/sinatra-%{version_rubygem_sinatra}.gem
bc4e95
 Source92: https://rubygems.org/downloads/tilt-%{version_rubygem_tilt}.gem
bc4e95
+Source93: https://rubygems.org/downloads/eventmachine-%{version_rubygem_eventmachine}.gem
bc4e95
+Source94: https://rubygems.org/downloads/daemons-%{version_rubygem_daemons}.gem
bc4e95
+Source95: https://rubygems.org/downloads/thin-%{version_rubygem_thin}.gem
bc4e95
 
bc4e95
 Source100: https://github.com/idevat/pcs-web-ui/archive/%{ui_commit}/%{ui_src_name}.tar.gz
bc4e95
 Source101: https://github.com/idevat/pcs-web-ui/releases/download/%{ui_commit}/pcs-web-ui-node-modules-%{ui_commit}.tar.xz
bc4e95
@@ -164,7 +170,9 @@ Recommends: overpass-fonts
bc4e95
 
bc4e95
 Provides: bundled(tornado) = %{tornado_version}
bc4e95
 Provides: bundled(backports) = %{version_rubygem_backports}
bc4e95
+Provides: bundled(daemons) = %{version_rubygem_daemons}
bc4e95
 Provides: bundled(ethon) = %{version_rubygem_ethon}
bc4e95
+Provides: bundled(eventmachine) = %{version_rubygem_eventmachine}
bc4e95
 Provides: bundled(ffi) = %{version_rubygem_ffi}
bc4e95
 Provides: bundled(json) = %{version_rubygem_json}
bc4e95
 Provides: bundled(mustermann) = %{version_rubygem_mustermann}
bc4e95
@@ -173,6 +181,7 @@ Provides: bundled(rack) = %{version_rubygem_rack}
bc4e95
 Provides: bundled(rack) = %{version_rubygem_rack_protection}
bc4e95
 Provides: bundled(rack) = %{version_rubygem_rack_test}
bc4e95
 Provides: bundled(sinatra) = %{version_rubygem_sinatra}
bc4e95
+Provides: bundled(thin) = %{version_rubygem_thin}
bc4e95
 Provides: bundled(tilt) = %{version_rubygem_tilt}
bc4e95
 
bc4e95
 %description
bc4e95
@@ -228,6 +237,9 @@ cp -f %SOURCE89 pcsd/vendor/cache
bc4e95
 cp -f %SOURCE90 pcsd/vendor/cache
bc4e95
 cp -f %SOURCE91 pcsd/vendor/cache
bc4e95
 cp -f %SOURCE92 pcsd/vendor/cache
bc4e95
+cp -f %SOURCE93 pcsd/vendor/cache
bc4e95
+cp -f %SOURCE94 pcsd/vendor/cache
bc4e95
+cp -f %SOURCE95 pcsd/vendor/cache
bc4e95
 
bc4e95
 
bc4e95
 # 3) dir for python bundles
bc4e95
@@ -262,15 +274,18 @@ gem install \
bc4e95
   --force --verbose -l --no-user-install %{gem_install_params} \
bc4e95
   -i %{rubygem_bundle_dir} \
bc4e95
   %{rubygem_cache_dir}/backports-%{version_rubygem_backports}.gem \
bc4e95
+  %{rubygem_cache_dir}/daemons-%{version_rubygem_daemons}.gem \
bc4e95
   %{rubygem_cache_dir}/ethon-%{version_rubygem_ethon}.gem \
bc4e95
+  %{rubygem_cache_dir}/eventmachine-%{version_rubygem_eventmachine}.gem \
bc4e95
   %{rubygem_cache_dir}/ffi-%{version_rubygem_ffi}.gem \
bc4e95
   %{rubygem_cache_dir}/json-%{version_rubygem_json}.gem \
bc4e95
   %{rubygem_cache_dir}/mustermann-%{version_rubygem_mustermann}.gem \
bc4e95
   %{rubygem_cache_dir}/open4-%{version_rubygem_open4}.gem \
bc4e95
-  %{rubygem_cache_dir}/rack-%{version_rubygem_rack}.gem \
bc4e95
   %{rubygem_cache_dir}/rack-protection-%{version_rubygem_rack_protection}.gem \
bc4e95
   %{rubygem_cache_dir}/rack-test-%{version_rubygem_rack_test}.gem \
bc4e95
+  %{rubygem_cache_dir}/rack-%{version_rubygem_rack}.gem \
bc4e95
   %{rubygem_cache_dir}/sinatra-%{version_rubygem_sinatra}.gem \
bc4e95
+  %{rubygem_cache_dir}/thin-%{version_rubygem_thin}.gem \
bc4e95
   %{rubygem_cache_dir}/tilt-%{version_rubygem_tilt}.gem \
bc4e95
   -- '--with-ldflags=-Wl,-z,relro -Wl,-z,ibt -Wl,-z,now -Wl,--gc-sections' \
bc4e95
      '--with-cflags=-O2 -ffunction-sections'
bc4e95
@@ -324,20 +339,31 @@ rm -r -v ${pcsd_dir}/test
bc4e95
 # remove javascript testing files
bc4e95
 rm -r -v ${pcsd_dir}/public/js/dev
bc4e95
 
bc4e95
+%posttrans
bc4e95
+# Make sure the new version of the daemon is runnning.
bc4e95
+# Also, make sure to start pcsd-ruby if it hasn't been started or even
bc4e95
+# installed before. This is done by restarting pcsd.service.
bc4e95
+%{_bindir}/systemctl daemon-reload
bc4e95
+%{_bindir}/systemctl try-restart pcsd.service
bc4e95
+
bc4e95
+
bc4e95
 %post
bc4e95
 %systemd_post pcsd.service
bc4e95
+%systemd_post pcsd-ruby.service
bc4e95
 
bc4e95
 %post -n %{pcs_snmp_pkg_name}
bc4e95
 %systemd_post pcs_snmp_agent.service
bc4e95
 
bc4e95
 %preun
bc4e95
 %systemd_preun pcsd.service
bc4e95
+%systemd_preun pcsd-ruby.service
bc4e95
 
bc4e95
 %preun -n %{pcs_snmp_pkg_name}
bc4e95
 %systemd_preun pcs_snmp_agent.service
bc4e95
 
bc4e95
 %postun
bc4e95
 %systemd_postun_with_restart pcsd.service
bc4e95
+%systemd_postun_with_restart pcsd-ruby.service
bc4e95
 
bc4e95
 %postun -n %{pcs_snmp_pkg_name}
bc4e95
 %systemd_postun_with_restart pcs_snmp_agent.service
bc4e95
@@ -357,6 +383,7 @@ rm -r -v ${pcsd_dir}/public/js/dev
bc4e95
 %{pcs_libdir}/pcsd/.bundle/config
bc4e95
 %{pcs_libdir}/pcs/bundled/packages/tornado*
bc4e95
 %{_unitdir}/pcsd.service
bc4e95
+%{_unitdir}/pcsd-ruby.service
bc4e95
 %{_datadir}/bash-completion/completions/pcs
bc4e95
 %{_sharedstatedir}/pcsd
bc4e95
 %{_sysconfdir}/pam.d/pcsd
bc4e95
@@ -374,6 +401,7 @@ rm -r -v ${pcsd_dir}/public/js/dev
bc4e95
 %{_mandir}/man8/pcsd.*
bc4e95
 %exclude %{pcs_libdir}/pcsd/*.debian
bc4e95
 %exclude %{pcs_libdir}/pcsd/pcsd.service
bc4e95
+%exclude %{pcs_libdir}/pcsd/pcsd-ruby.service
bc4e95
 %exclude %{pcs_libdir}/pcsd/pcsd.conf
bc4e95
 %exclude %{pcs_libdir}/pcsd/pcsd.8
bc4e95
 %exclude %{pcs_libdir}/pcsd/public/js/dev/*
bc4e95
diff --git a/pcs/daemon/app/sinatra_ui.py b/pcs/daemon/app/sinatra_ui.py
bc4e95
index 1348134d..5315a48f 100644
bc4e95
--- a/pcs/daemon/app/sinatra_ui.py
bc4e95
+++ b/pcs/daemon/app/sinatra_ui.py
bc4e95
@@ -153,7 +153,7 @@ def get_routes(
bc4e95
         # The protection by session was moved from ruby code to python code
bc4e95
         # (tornado).
bc4e95
         (
bc4e95
-            r"/($|manage$|permissions$|managec/.+/main)",
bc4e95
+            r"/($|manage/?$|permissions/?$|managec/.+/main)",
bc4e95
             SinatraGuiProtected,
bc4e95
             {**sessions, **ruby_wrapper}
bc4e95
         ),
bc4e95
diff --git a/pcs/daemon/env.py b/pcs/daemon/env.py
bc4e95
index 54a9819f..26cdcf9b 100644
bc4e95
--- a/pcs/daemon/env.py
bc4e95
+++ b/pcs/daemon/env.py
bc4e95
@@ -15,7 +15,6 @@ from pcs.lib.validate import is_port_number
bc4e95
 # Relative location instead of system location is used for development purposes.
bc4e95
 PCSD_LOCAL_DIR = realpath(dirname(abspath(__file__)) + "/../../pcsd")
bc4e95
 
bc4e95
-PCSD_CMDLINE_ENTRY_RB_SCRIPT = "sinatra_cmdline_wrapper.rb"
bc4e95
 PCSD_STATIC_FILES_DIR_NAME = "public"
bc4e95
 
bc4e95
 PCSD_PORT = "PCSD_PORT"
bc4e95
@@ -26,12 +25,8 @@ NOTIFY_SOCKET = "NOTIFY_SOCKET"
bc4e95
 PCSD_DEBUG = "PCSD_DEBUG"
bc4e95
 PCSD_DISABLE_GUI = "PCSD_DISABLE_GUI"
bc4e95
 PCSD_SESSION_LIFETIME = "PCSD_SESSION_LIFETIME"
bc4e95
-GEM_HOME = "GEM_HOME"
bc4e95
 PCSD_DEV = "PCSD_DEV"
bc4e95
-PCSD_CMDLINE_ENTRY = "PCSD_CMDLINE_ENTRY"
bc4e95
 PCSD_STATIC_FILES_DIR = "PCSD_STATIC_FILES_DIR"
bc4e95
-HTTPS_PROXY = "HTTPS_PROXY"
bc4e95
-NO_PROXY = "NO_PROXY"
bc4e95
 
bc4e95
 Env = namedtuple("Env", [
bc4e95
     PCSD_PORT,
bc4e95
@@ -42,11 +37,7 @@ Env = namedtuple("Env", [
bc4e95
     PCSD_DEBUG,
bc4e95
     PCSD_DISABLE_GUI,
bc4e95
     PCSD_SESSION_LIFETIME,
bc4e95
-    GEM_HOME,
bc4e95
-    PCSD_CMDLINE_ENTRY,
bc4e95
     PCSD_STATIC_FILES_DIR,
bc4e95
-    HTTPS_PROXY,
bc4e95
-    NO_PROXY,
bc4e95
     PCSD_DEV,
bc4e95
     "has_errors",
bc4e95
 ])
bc4e95
@@ -62,11 +53,7 @@ def prepare_env(environ, logger=None):
bc4e95
         loader.pcsd_debug(),
bc4e95
         loader.pcsd_disable_gui(),
bc4e95
         loader.session_lifetime(),
bc4e95
-        loader.gem_home(),
bc4e95
-        loader.pcsd_cmdline_entry(),
bc4e95
         loader.pcsd_static_files_dir(),
bc4e95
-        loader.https_proxy(),
bc4e95
-        loader.no_proxy(),
bc4e95
         loader.pcsd_dev(),
bc4e95
         loader.has_errors(),
bc4e95
     )
bc4e95
@@ -173,20 +160,6 @@ class EnvLoader:
bc4e95
     def pcsd_debug(self):
bc4e95
         return self.__has_true_in_environ(PCSD_DEBUG)
bc4e95
 
bc4e95
-    def gem_home(self):
bc4e95
-        if settings.pcsd_gem_path is None:
bc4e95
-            return None
bc4e95
-        return self.__in_pcsd_path(
bc4e95
-            settings.pcsd_gem_path,
bc4e95
-            "Ruby gem location"
bc4e95
-        )
bc4e95
-
bc4e95
-    def pcsd_cmdline_entry(self):
bc4e95
-        return self.__in_pcsd_path(
bc4e95
-            PCSD_CMDLINE_ENTRY_RB_SCRIPT,
bc4e95
-            "Ruby handlers entrypoint"
bc4e95
-        )
bc4e95
-
bc4e95
     def pcsd_static_files_dir(self):
bc4e95
         return self.__in_pcsd_path(
bc4e95
             PCSD_STATIC_FILES_DIR_NAME,
bc4e95
@@ -194,15 +167,6 @@ class EnvLoader:
bc4e95
             existence_required=not self.pcsd_disable_gui()
bc4e95
         )
bc4e95
 
bc4e95
-    def https_proxy(self):
bc4e95
-        for key in ["https_proxy", HTTPS_PROXY, "all_proxy", "ALL_PROXY"]:
bc4e95
-            if key in self.environ:
bc4e95
-                return self.environ[key]
bc4e95
-        return None
bc4e95
-
bc4e95
-    def no_proxy(self):
bc4e95
-        return self.environ.get("no_proxy", self.environ.get(NO_PROXY, None))
bc4e95
-
bc4e95
     @lru_cache()
bc4e95
     def pcsd_dev(self):
bc4e95
         return self.__has_true_in_environ(PCSD_DEV)
bc4e95
diff --git a/pcs/daemon/ruby_pcsd.py b/pcs/daemon/ruby_pcsd.py
bc4e95
index 5bdaffeb..e612f8da 100644
bc4e95
--- a/pcs/daemon/ruby_pcsd.py
bc4e95
+++ b/pcs/daemon/ruby_pcsd.py
bc4e95
@@ -1,14 +1,16 @@
bc4e95
 import json
bc4e95
 import logging
bc4e95
-import os.path
bc4e95
-from base64 import b64decode
bc4e95
+from base64 import b64decode, b64encode, binascii
bc4e95
 from collections import namedtuple
bc4e95
 from time import time as now
bc4e95
 
bc4e95
-from tornado.gen import multi, convert_yielded
bc4e95
+import pycurl
bc4e95
+from tornado.gen import convert_yielded
bc4e95
 from tornado.web import HTTPError
bc4e95
 from tornado.httputil import split_host_and_port, HTTPServerRequest
bc4e95
-from tornado.process import Subprocess
bc4e95
+from tornado.httpclient import AsyncHTTPClient
bc4e95
+from tornado.curl_httpclient import CurlError
bc4e95
+
bc4e95
 
bc4e95
 from pcs.daemon import log
bc4e95
 
bc4e95
@@ -33,7 +35,7 @@ class SinatraResult(namedtuple("SinatraResult", "headers, status, body")):
bc4e95
         return cls(
bc4e95
             response["headers"],
bc4e95
             response["status"],
bc4e95
-            b64decode(response["body"])
bc4e95
+            response["body"]
bc4e95
         )
bc4e95
 
bc4e95
 def log_group_id_generator():
bc4e95
@@ -58,24 +60,12 @@ def process_response_logs(rb_log_list):
bc4e95
             group_id=group_id
bc4e95
         )
bc4e95
 
bc4e95
-def log_communication(request_json, stdout, stderr):
bc4e95
-    log.pcsd.debug("Request for ruby pcsd wrapper: '%s'", request_json)
bc4e95
-    log.pcsd.debug("Response stdout from ruby pcsd wrapper: '%s'", stdout)
bc4e95
-    log.pcsd.debug("Response stderr from ruby pcsd wrapper: '%s'", stderr)
bc4e95
-
bc4e95
 class Wrapper:
bc4e95
-    # pylint: disable=too-many-instance-attributes
bc4e95
-    def __init__(
bc4e95
-        self, pcsd_cmdline_entry, gem_home=None, debug=False,
bc4e95
-        ruby_executable="ruby", https_proxy=None, no_proxy=None
bc4e95
-    ):
bc4e95
-        self.__gem_home = gem_home
bc4e95
-        self.__pcsd_cmdline_entry = pcsd_cmdline_entry
bc4e95
-        self.__pcsd_dir = os.path.dirname(pcsd_cmdline_entry)
bc4e95
-        self.__ruby_executable = ruby_executable
bc4e95
+    def __init__(self, pcsd_ruby_socket, debug=False):
bc4e95
         self.__debug = debug
bc4e95
-        self.__https_proxy = https_proxy
bc4e95
-        self.__no_proxy = no_proxy
bc4e95
+        AsyncHTTPClient.configure('tornado.curl_httpclient.CurlAsyncHTTPClient')
bc4e95
+        self.__client = AsyncHTTPClient()
bc4e95
+        self.__pcsd_ruby_socket = pcsd_ruby_socket
bc4e95
 
bc4e95
     @staticmethod
bc4e95
     def get_sinatra_request(request: HTTPServerRequest):
bc4e95
@@ -102,55 +92,76 @@ class Wrapper:
bc4e95
             "rack.input": request.body.decode("utf8"),
bc4e95
         }}
bc4e95
 
bc4e95
+    def prepare_curl_callback(self, curl):
bc4e95
+        curl.setopt(pycurl.UNIX_SOCKET_PATH, self.__pcsd_ruby_socket)
bc4e95
+        curl.setopt(pycurl.TIMEOUT, 70)
bc4e95
+
bc4e95
     async def send_to_ruby(self, request_json):
bc4e95
-        env = {
bc4e95
-            "PCSD_DEBUG": "true" if self.__debug else "false"
bc4e95
-        }
bc4e95
-        if self.__gem_home is not None:
bc4e95
-            env["GEM_HOME"] = self.__gem_home
bc4e95
-
bc4e95
-        if self.__no_proxy is not None:
bc4e95
-            env["NO_PROXY"] = self.__no_proxy
bc4e95
-        if self.__https_proxy is not None:
bc4e95
-            env["HTTPS_PROXY"] = self.__https_proxy
bc4e95
-
bc4e95
-        pcsd_ruby = Subprocess(
bc4e95
-            [
bc4e95
-                self.__ruby_executable, "-I",
bc4e95
-                self.__pcsd_dir,
bc4e95
-                self.__pcsd_cmdline_entry
bc4e95
-            ],
bc4e95
-            stdin=Subprocess.STREAM,
bc4e95
-            stdout=Subprocess.STREAM,
bc4e95
-            stderr=Subprocess.STREAM,
bc4e95
-            env=env
bc4e95
-        )
bc4e95
-        await pcsd_ruby.stdin.write(str.encode(request_json))
bc4e95
-        pcsd_ruby.stdin.close()
bc4e95
-        return await multi([
bc4e95
-            pcsd_ruby.stdout.read_until_close(),
bc4e95
-            pcsd_ruby.stderr.read_until_close(),
bc4e95
-            pcsd_ruby.wait_for_exit(raise_error=False),
bc4e95
-        ])
bc4e95
+        # We do not need location for communication with ruby itself since we
bc4e95
+        # communicate via unix socket. But it is required by AsyncHTTPClient so
bc4e95
+        # "localhost" is used.
bc4e95
+        tornado_request = b64encode(request_json.encode()).decode()
bc4e95
+        return (await self.__client.fetch(
bc4e95
+            "localhost",
bc4e95
+            method="POST",
bc4e95
+            body=f"TORNADO_REQUEST={tornado_request}",
bc4e95
+            prepare_curl_callback=self.prepare_curl_callback,
bc4e95
+        )).body
bc4e95
 
bc4e95
     async def run_ruby(self, request_type, request=None):
bc4e95
+        """
bc4e95
+        request_type: SINATRA_GUI|SINATRA_REMOTE|SYNC_CONFIGS
bc4e95
+        request: result of get_sinatra_request|None
bc4e95
+            i.e. it has structure returned by get_sinatra_request if the request
bc4e95
+            is not None - so we can get SERVER_NAME and  SERVER_PORT
bc4e95
+        """
bc4e95
         request = request or {}
bc4e95
         request.update({"type": request_type})
bc4e95
         request_json = json.dumps(request)
bc4e95
-        stdout, stderr, dummy_status = await self.send_to_ruby(request_json)
bc4e95
+
bc4e95
+        if self.__debug:
bc4e95
+            log.pcsd.debug("Ruby daemon request: '%s'", request_json)
bc4e95
         try:
bc4e95
-            response = json.loads(stdout)
bc4e95
-        except json.JSONDecodeError as e:
bc4e95
-            self.__log_bad_response(
bc4e95
-                f"Cannot decode json from ruby pcsd wrapper: '{e}'",
bc4e95
-                request_json, stdout, stderr
bc4e95
+            ruby_response = await self.send_to_ruby(request_json)
bc4e95
+        except CurlError as e:
bc4e95
+            log.pcsd.error(
bc4e95
+                "Cannot connect to ruby daemon (message: '%s'). Is it running?",
bc4e95
+                e
bc4e95
             )
bc4e95
             raise HTTPError(500)
bc4e95
-        else:
bc4e95
-            if self.__debug:
bc4e95
-                log_communication(request_json, stdout, stderr)
bc4e95
-            process_response_logs(response["logs"])
bc4e95
+
bc4e95
+        try:
bc4e95
+            response = json.loads(ruby_response)
bc4e95
+            if "error" in response:
bc4e95
+                log.pcsd.error(
bc4e95
+                    "Ruby daemon response contains an error: '%s'",
bc4e95
+                    json.dumps(response)
bc4e95
+                )
bc4e95
+                raise HTTPError(500)
bc4e95
+
bc4e95
+            logs = response.pop("logs", [])
bc4e95
+            if "body" in response:
bc4e95
+                body = b64decode(response.pop("body"))
bc4e95
+                if self.__debug:
bc4e95
+                    log.pcsd.debug(
bc4e95
+                        "Ruby daemon response (without logs and body): '%s'",
bc4e95
+                        json.dumps(response)
bc4e95
+                    )
bc4e95
+                    log.pcsd.debug("Ruby daemon response body: '%s'", body)
bc4e95
+                response["body"] = body
bc4e95
+
bc4e95
+            elif self.__debug:
bc4e95
+                log.pcsd.debug(
bc4e95
+                    "Ruby daemon response (without logs): '%s'",
bc4e95
+                    json.dumps(response)
bc4e95
+                )
bc4e95
+            process_response_logs(logs)
bc4e95
             return response
bc4e95
+        except (json.JSONDecodeError, binascii.Error) as e:
bc4e95
+            if self.__debug:
bc4e95
+                log.pcsd.debug("Ruby daemon response: '%s'", ruby_response)
bc4e95
+            log.pcsd.error("Cannot decode json from ruby pcsd wrapper: '%s'", e)
bc4e95
+            raise HTTPError(500)
bc4e95
 
bc4e95
     async def request_gui(
bc4e95
         self, request: HTTPServerRequest, user, groups, is_authenticated
bc4e95
@@ -186,8 +197,3 @@ class Wrapper:
bc4e95
         except HTTPError:
bc4e95
             log.pcsd.error("Config synchronization failed")
bc4e95
             return int(now()) + DEFAULT_SYNC_CONFIG_DELAY
bc4e95
-
bc4e95
-    def __log_bad_response(self, error_message, request_json, stdout, stderr):
bc4e95
-        log.pcsd.error(error_message)
bc4e95
-        if self.__debug:
bc4e95
-            log_communication(request_json, stdout, stderr)
bc4e95
diff --git a/pcs/daemon/run.py b/pcs/daemon/run.py
bc4e95
index bafd9f3c..874ee2f1 100644
bc4e95
--- a/pcs/daemon/run.py
bc4e95
+++ b/pcs/daemon/run.py
bc4e95
@@ -65,6 +65,8 @@ def configure_app(
bc4e95
                 # old web ui by default
bc4e95
                 [(r"/", RedirectHandler, dict(url="/manage"))]
bc4e95
                 +
bc4e95
+                [(r"/ui", RedirectHandler, dict(url="/ui/"))]
bc4e95
+                +
bc4e95
                 ui.get_routes(
bc4e95
                     url_prefix="/ui/",
bc4e95
                     app_dir=os.path.join(public_dir, "ui"),
bc4e95
@@ -101,12 +103,8 @@ def main():
bc4e95
 
bc4e95
     sync_config_lock = Lock()
bc4e95
     ruby_pcsd_wrapper = ruby_pcsd.Wrapper(
bc4e95
-        pcsd_cmdline_entry=env.PCSD_CMDLINE_ENTRY,
bc4e95
-        gem_home=env.GEM_HOME,
bc4e95
+        settings.pcsd_ruby_socket,
bc4e95
         debug=env.PCSD_DEBUG,
bc4e95
-        ruby_executable=settings.ruby_executable,
bc4e95
-        https_proxy=env.HTTPS_PROXY,
bc4e95
-        no_proxy=env.NO_PROXY,
bc4e95
     )
bc4e95
     make_app = configure_app(
bc4e95
         session.Storage(env.PCSD_SESSION_LIFETIME),
bc4e95
diff --git a/pcs/settings_default.py b/pcs/settings_default.py
bc4e95
index 6d8f33ac..f761ce43 100644
bc4e95
--- a/pcs/settings_default.py
bc4e95
+++ b/pcs/settings_default.py
bc4e95
@@ -43,6 +43,7 @@ cibadmin = os.path.join(pacemaker_binaries, "cibadmin")
bc4e95
 crm_mon_schema = '/usr/share/pacemaker/crm_mon.rng'
bc4e95
 agent_metadata_schema = "/usr/share/resource-agents/ra-api-1.dtd"
bc4e95
 pcsd_var_location = "/var/lib/pcsd/"
bc4e95
+pcsd_ruby_socket = "/run/pcsd-ruby.socket"
bc4e95
 pcsd_cert_location = os.path.join(pcsd_var_location, "pcsd.crt")
bc4e95
 pcsd_key_location = os.path.join(pcsd_var_location, "pcsd.key")
bc4e95
 pcsd_known_hosts_location = os.path.join(pcsd_var_location, "known-hosts")
bc4e95
diff --git a/pcs_test/tier0/daemon/app/fixtures_app.py b/pcs_test/tier0/daemon/app/fixtures_app.py
bc4e95
index 2e4feba4..8d5b8f4c 100644
bc4e95
--- a/pcs_test/tier0/daemon/app/fixtures_app.py
bc4e95
+++ b/pcs_test/tier0/daemon/app/fixtures_app.py
bc4e95
@@ -1,4 +1,3 @@
bc4e95
-from base64 import b64encode
bc4e95
 from pprint import pformat
bc4e95
 from urllib.parse import urlencode
bc4e95
 
bc4e95
@@ -30,7 +29,7 @@ class RubyPcsdWrapper(ruby_pcsd.Wrapper):
bc4e95
         return {
bc4e95
             "headers": self.headers,
bc4e95
             "status": self.status_code,
bc4e95
-            "body": b64encode(self.body),
bc4e95
+            "body": self.body,
bc4e95
         }
bc4e95
 
bc4e95
 class AppTest(AsyncHTTPTestCase):
bc4e95
diff --git a/pcs_test/tier0/daemon/test_env.py b/pcs_test/tier0/daemon/test_env.py
bc4e95
index 9e78eafd..e2f7f5b1 100644
bc4e95
--- a/pcs_test/tier0/daemon/test_env.py
bc4e95
+++ b/pcs_test/tier0/daemon/test_env.py
bc4e95
@@ -41,11 +41,7 @@ class Prepare(TestCase, create_setup_patch_mixin(env)):
bc4e95
             env.PCSD_DEBUG: False,
bc4e95
             env.PCSD_DISABLE_GUI: False,
bc4e95
             env.PCSD_SESSION_LIFETIME: settings.gui_session_lifetime_seconds,
bc4e95
-            env.GEM_HOME: pcsd_dir(settings.pcsd_gem_path),
bc4e95
-            env.PCSD_CMDLINE_ENTRY: pcsd_dir(env.PCSD_CMDLINE_ENTRY_RB_SCRIPT),
bc4e95
             env.PCSD_STATIC_FILES_DIR: pcsd_dir(env.PCSD_STATIC_FILES_DIR_NAME),
bc4e95
-            env.HTTPS_PROXY: None,
bc4e95
-            env.NO_PROXY: None,
bc4e95
             env.PCSD_DEV: False,
bc4e95
             "has_errors": False,
bc4e95
         }
bc4e95
@@ -77,8 +73,6 @@ class Prepare(TestCase, create_setup_patch_mixin(env)):
bc4e95
             env.PCSD_DISABLE_GUI: "true",
bc4e95
             env.PCSD_SESSION_LIFETIME: str(session_lifetime),
bc4e95
             env.PCSD_DEV: "true",
bc4e95
-            env.HTTPS_PROXY: "proxy1",
bc4e95
-            env.NO_PROXY: "host",
bc4e95
             env.PCSD_DEV: "true",
bc4e95
         }
bc4e95
         self.assert_environ_produces_modified_pcsd_env(
bc4e95
@@ -92,15 +86,9 @@ class Prepare(TestCase, create_setup_patch_mixin(env)):
bc4e95
                 env.PCSD_DEBUG: True,
bc4e95
                 env.PCSD_DISABLE_GUI: True,
bc4e95
                 env.PCSD_SESSION_LIFETIME: session_lifetime,
bc4e95
-                env.GEM_HOME: pcsd_dir(settings.pcsd_gem_path),
bc4e95
-                env.PCSD_CMDLINE_ENTRY: pcsd_dir(
bc4e95
-                    env.PCSD_CMDLINE_ENTRY_RB_SCRIPT
bc4e95
-                ),
bc4e95
                 env.PCSD_STATIC_FILES_DIR: pcsd_dir(
bc4e95
                     env.PCSD_STATIC_FILES_DIR_NAME
bc4e95
                 ),
bc4e95
-                env.HTTPS_PROXY: environ[env.HTTPS_PROXY],
bc4e95
-                env.NO_PROXY: environ[env.NO_PROXY],
bc4e95
                 env.PCSD_DEV: True,
bc4e95
             },
bc4e95
         )
bc4e95
@@ -167,13 +155,6 @@ class Prepare(TestCase, create_setup_patch_mixin(env)):
bc4e95
         self.assert_environ_produces_modified_pcsd_env(
bc4e95
             specific_env_values={"has_errors": True},
bc4e95
             errors=[
bc4e95
-                f"Ruby gem location '{pcsd_dir(settings.pcsd_gem_path)}'"
bc4e95
-                    " does not exist"
bc4e95
-                ,
bc4e95
-                "Ruby handlers entrypoint"
bc4e95
-                    f" '{pcsd_dir(env.PCSD_CMDLINE_ENTRY_RB_SCRIPT)}'"
bc4e95
-                    " does not exist"
bc4e95
-                ,
bc4e95
                 "Directory with web UI assets"
bc4e95
                     f" '{pcsd_dir(env.PCSD_STATIC_FILES_DIR_NAME)}'"
bc4e95
                     " does not exist"
bc4e95
@@ -181,54 +162,13 @@ class Prepare(TestCase, create_setup_patch_mixin(env)):
bc4e95
             ]
bc4e95
         )
bc4e95
 
bc4e95
-    def test_errors_on_missing_paths_disabled_gui(self):
bc4e95
+    def test_no_errors_on_missing_paths_disabled_gui(self):
bc4e95
         self.path_exists.return_value = False
bc4e95
-        pcsd_dir = partial(join_path, settings.pcsd_exec_location)
bc4e95
         self.assert_environ_produces_modified_pcsd_env(
bc4e95
             environ={env.PCSD_DISABLE_GUI: "true"},
bc4e95
             specific_env_values={
bc4e95
                 env.PCSD_DISABLE_GUI: True,
bc4e95
-                "has_errors": True,
bc4e95
+                "has_errors": False,
bc4e95
             },
bc4e95
-            errors=[
bc4e95
-                f"Ruby gem location '{pcsd_dir(settings.pcsd_gem_path)}'"
bc4e95
-                    " does not exist"
bc4e95
-                ,
bc4e95
-                "Ruby handlers entrypoint"
bc4e95
-                    f" '{pcsd_dir(env.PCSD_CMDLINE_ENTRY_RB_SCRIPT)}'"
bc4e95
-                    " does not exist"
bc4e95
-                ,
bc4e95
-            ]
bc4e95
+            errors=[]
bc4e95
         )
bc4e95
-
bc4e95
-    def test_lower_case_no_proxy_has_precedence(self):
bc4e95
-        def it_selects(proxy_value):
bc4e95
-            self.assert_environ_produces_modified_pcsd_env(
bc4e95
-                environ=environ,
bc4e95
-                specific_env_values={env.NO_PROXY: proxy_value}
bc4e95
-            )
bc4e95
-
bc4e95
-        environ = {"NO_PROXY": "no_proxy_1"}
bc4e95
-        it_selects("no_proxy_1")
bc4e95
-
bc4e95
-        environ["no_proxy"] = "no_proxy_2"
bc4e95
-        it_selects("no_proxy_2")
bc4e95
-
bc4e95
-    def test_http_proxy_is_setup_by_precedence(self):
bc4e95
-        def it_selects(proxy_value):
bc4e95
-            self.assert_environ_produces_modified_pcsd_env(
bc4e95
-                environ=environ,
bc4e95
-                specific_env_values={env.HTTPS_PROXY: proxy_value}
bc4e95
-            )
bc4e95
-
bc4e95
-        environ = {"ALL_PROXY": "all_proxy_1"}
bc4e95
-        it_selects("all_proxy_1")
bc4e95
-
bc4e95
-        environ["all_proxy"] = "all_proxy_2"
bc4e95
-        it_selects("all_proxy_2")
bc4e95
-
bc4e95
-        environ["HTTPS_PROXY"] = "https_proxy_1"
bc4e95
-        it_selects("https_proxy_1")
bc4e95
-
bc4e95
-        environ["https_proxy"] = "https_proxy_2"
bc4e95
-        it_selects("https_proxy_2")
bc4e95
diff --git a/pcs_test/tier0/daemon/test_ruby_pcsd.py b/pcs_test/tier0/daemon/test_ruby_pcsd.py
bc4e95
index d7fd71a0..28f14c87 100644
bc4e95
--- a/pcs_test/tier0/daemon/test_ruby_pcsd.py
bc4e95
+++ b/pcs_test/tier0/daemon/test_ruby_pcsd.py
bc4e95
@@ -16,10 +16,7 @@ from pcs.daemon import ruby_pcsd
bc4e95
 logging.getLogger("pcs.daemon").setLevel(logging.CRITICAL)
bc4e95
 
bc4e95
 def create_wrapper():
bc4e95
-    return ruby_pcsd.Wrapper(
bc4e95
-        rc("/path/to/gem_home"),
bc4e95
-        rc("/path/to/pcsd/cmdline/entry"),
bc4e95
-    )
bc4e95
+    return ruby_pcsd.Wrapper(rc("/path/to/ruby_socket"))
bc4e95
 
bc4e95
 def create_http_request():
bc4e95
     return HTTPServerRequest(
bc4e95
@@ -63,9 +60,7 @@ patch_ruby_pcsd = create_patcher(ruby_pcsd)
bc4e95
 
bc4e95
 class RunRuby(AsyncTestCase):
bc4e95
     def setUp(self):
bc4e95
-        self.stdout = ""
bc4e95
-        self.stderr = ""
bc4e95
-        self.exit_status = 0
bc4e95
+        self.ruby_response = ""
bc4e95
         self.request = self.create_request()
bc4e95
         self.wrapper = create_wrapper()
bc4e95
         patcher = mock.patch.object(
bc4e95
@@ -79,14 +74,14 @@ class RunRuby(AsyncTestCase):
bc4e95
 
bc4e95
     async def send_to_ruby(self, request_json):
bc4e95
         self.assertEqual(json.loads(request_json), self.request)
bc4e95
-        return self.stdout, self.stderr, self.exit_status
bc4e95
+        return self.ruby_response
bc4e95
 
bc4e95
     @staticmethod
bc4e95
     def create_request(_type=ruby_pcsd.SYNC_CONFIGS):
bc4e95
         return {"type": _type}
bc4e95
 
bc4e95
     def set_run_result(self, run_result):
bc4e95
-        self.stdout = json.dumps({**run_result, "logs": []})
bc4e95
+        self.ruby_response = json.dumps({**run_result, "logs": []})
bc4e95
 
bc4e95
     def assert_sinatra_result(self, result, headers, status, body):
bc4e95
         self.assertEqual(result.headers, headers)
bc4e95
diff --git a/pcsd/Gemfile b/pcsd/Gemfile
bc4e95
index 27898f71..716991a6 100644
bc4e95
--- a/pcsd/Gemfile
bc4e95
+++ b/pcsd/Gemfile
bc4e95
@@ -10,3 +10,4 @@ gem 'json'
bc4e95
 gem 'open4'
bc4e95
 gem 'ffi'
bc4e95
 gem 'ethon'
bc4e95
+gem 'thin'
bc4e95
diff --git a/pcsd/Gemfile.lock b/pcsd/Gemfile.lock
bc4e95
index 6f833888..c8b02a94 100644
bc4e95
--- a/pcsd/Gemfile.lock
bc4e95
+++ b/pcsd/Gemfile.lock
bc4e95
@@ -2,8 +2,10 @@ GEM
bc4e95
   remote: https://rubygems.org/
bc4e95
   specs:
bc4e95
     backports (3.11.4)
bc4e95
+    daemons (1.3.1)
bc4e95
     ethon (0.11.0)
bc4e95
       ffi (>= 1.3.0)
bc4e95
+    eventmachine (1.2.7)
bc4e95
     ffi (1.9.25)
bc4e95
     json (2.1.0)
bc4e95
     mustermann (1.0.3)
bc4e95
@@ -18,6 +20,10 @@ GEM
bc4e95
       rack (~> 2.0)
bc4e95
       rack-protection (= 2.0.4)
bc4e95
       tilt (~> 2.0)
bc4e95
+    thin (1.7.2)
bc4e95
+      daemons (~> 1.0, >= 1.0.9)
bc4e95
+      eventmachine (~> 1.0, >= 1.0.4)
bc4e95
+      rack (>= 1, < 3)
bc4e95
     tilt (2.0.9)
bc4e95
 
bc4e95
 PLATFORMS
bc4e95
@@ -33,6 +39,7 @@ DEPENDENCIES
bc4e95
   rack-protection
bc4e95
   rack-test
bc4e95
   sinatra
bc4e95
+  thin
bc4e95
   tilt
bc4e95
 
bc4e95
 BUNDLED WITH
bc4e95
diff --git a/pcsd/Makefile b/pcsd/Makefile
bc4e95
index 5fe3f3f3..5dde50e3 100644
bc4e95
--- a/pcsd/Makefile
bc4e95
+++ b/pcsd/Makefile
bc4e95
@@ -26,6 +26,9 @@ build_gems_without_bundler:
bc4e95
 	vendor/cache/rack-test-1.1.0.gem \
bc4e95
 	vendor/cache/sinatra-2.0.4.gem \
bc4e95
 	vendor/cache/tilt-2.0.9.gem \
bc4e95
+	vendor/cache/eventmachine-1.2.7.gem \
bc4e95
+	vendor/cache/daemons-1.3.1.gem \
bc4e95
+	vendor/cache/thin-1.7.2.gem \
bc4e95
 	-- '--with-ldflags="-Wl,-z,now -Wl,-z,relro"'
bc4e95
 
bc4e95
 get_gems:
bc4e95
diff --git a/pcsd/bootstrap.rb b/pcsd/bootstrap.rb
bc4e95
index ec6b535c..fc9d9b8c 100644
bc4e95
--- a/pcsd/bootstrap.rb
bc4e95
+++ b/pcsd/bootstrap.rb
bc4e95
@@ -51,8 +51,23 @@ if not defined? $cur_node_name
bc4e95
   $cur_node_name = `/bin/hostname`.chomp
bc4e95
 end
bc4e95
 
bc4e95
-def configure_logger(log_device)
bc4e95
-  logger = Logger.new(log_device)
bc4e95
+def configure_logger()
bc4e95
+  logger = Logger.new(StringIO.new())
bc4e95
+  logger.formatter = proc {|severity, datetime, progname, msg|
bc4e95
+    if Thread.current.key?(:pcsd_logger_container)
bc4e95
+      Thread.current[:pcsd_logger_container] << {
bc4e95
+        :level => severity,
bc4e95
+        :timestamp_usec => (datetime.to_f * 1000000).to_i,
bc4e95
+        :message => msg,
bc4e95
+      }
bc4e95
+    else
bc4e95
+      STDERR.puts("#{datetime} #{progname} #{severity} #{msg}")
bc4e95
+    end
bc4e95
+  }
bc4e95
+  return logger
bc4e95
+end
bc4e95
+
bc4e95
+def early_log(logger)
bc4e95
   if ENV['PCSD_DEBUG'] and ENV['PCSD_DEBUG'].downcase == "true" then
bc4e95
     logger.level = Logger::DEBUG
bc4e95
     logger.info "PCSD Debugging enabled"
bc4e95
@@ -65,7 +80,6 @@ def configure_logger(log_device)
bc4e95
   else
bc4e95
     logger.debug "Detected systemd is not in use"
bc4e95
   end
bc4e95
-  return logger
bc4e95
 end
bc4e95
 
bc4e95
 def get_capabilities(logger)
bc4e95
diff --git a/pcsd/cfgsync.rb b/pcsd/cfgsync.rb
bc4e95
index 16bcfbdc..1cab512e 100644
bc4e95
--- a/pcsd/cfgsync.rb
bc4e95
+++ b/pcsd/cfgsync.rb
bc4e95
@@ -468,7 +468,8 @@ module Cfgsync
bc4e95
       node_response = {}
bc4e95
       threads = []
bc4e95
       @nodes.each { |node|
bc4e95
-        threads << Thread.new {
bc4e95
+        threads << Thread.new(Thread.current[:pcsd_logger_container]) { |logger|
bc4e95
+          Thread.current[:pcsd_logger_container] = logger
bc4e95
           code, out = send_request_with_token(
bc4e95
             @auth_user, node, 'set_configs', true, data, true, nil, 30,
bc4e95
             @additional_known_hosts
bc4e95
@@ -616,7 +617,8 @@ module Cfgsync
bc4e95
       node_configs = {}
bc4e95
       connected_to = {}
bc4e95
       nodes.each { |node|
bc4e95
-        threads << Thread.new {
bc4e95
+        threads << Thread.new(Thread.current[:pcsd_logger_container]) { |logger|
bc4e95
+          Thread.current[:pcsd_logger_container] = logger
bc4e95
           code, out = send_request_with_token(
bc4e95
             @auth_user, node, 'get_configs', false, data, true, nil, nil,
bc4e95
             @additional_known_hosts
bc4e95
diff --git a/pcsd/pcs.rb b/pcsd/pcs.rb
bc4e95
index 7b991ac0..9a0efb46 100644
bc4e95
--- a/pcsd/pcs.rb
bc4e95
+++ b/pcsd/pcs.rb
bc4e95
@@ -923,7 +923,8 @@ def is_auth_against_nodes(auth_user, node_names, timeout=10)
bc4e95
   offline_nodes = []
bc4e95
 
bc4e95
   node_names.uniq.each { |node_name|
bc4e95
-    threads << Thread.new {
bc4e95
+    threads << Thread.new(Thread.current[:pcsd_logger_container]) { |logger|
bc4e95
+      Thread.current[:pcsd_logger_container] = logger
bc4e95
       code, response = send_request_with_token(
bc4e95
         auth_user, node_name, 'check_auth', false, {}, true, nil, timeout
bc4e95
       )
bc4e95
@@ -963,7 +964,8 @@ def pcs_auth(auth_user, nodes)
bc4e95
   auth_responses = {}
bc4e95
   threads = []
bc4e95
   nodes.each { |node_name, node_data|
bc4e95
-    threads << Thread.new {
bc4e95
+    threads << Thread.new(Thread.current[:pcsd_logger_container]) { |logger|
bc4e95
+      Thread.current[:pcsd_logger_container] = logger
bc4e95
       begin
bc4e95
         addr = node_data.fetch('dest_list').fetch(0).fetch('addr')
bc4e95
         port = node_data.fetch('dest_list').fetch(0).fetch('port')
bc4e95
@@ -1199,7 +1201,8 @@ def cluster_status_from_nodes(auth_user, cluster_nodes, cluster_name)
bc4e95
 
bc4e95
   threads = []
bc4e95
   cluster_nodes.uniq.each { |node|
bc4e95
-    threads << Thread.new {
bc4e95
+    threads << Thread.new(Thread.current[:pcsd_logger_container]) { |logger|
bc4e95
+      Thread.current[:pcsd_logger_container] = logger
bc4e95
       code, response = send_request_with_token(
bc4e95
         auth_user,
bc4e95
         node,
bc4e95
diff --git a/pcsd/pcsd-cli.rb b/pcsd/pcsd-cli.rb
bc4e95
index 942bae84..4daa93ba 100755
bc4e95
--- a/pcsd/pcsd-cli.rb
bc4e95
+++ b/pcsd/pcsd-cli.rb
bc4e95
@@ -29,7 +29,8 @@ end
bc4e95
 auth_user = {}
bc4e95
 PCS = get_pcs_path()
bc4e95
 $logger_device = StringIO.new
bc4e95
-$logger = configure_logger($logger_device)
bc4e95
+$logger = Logger.new($logger_device)
bc4e95
+early_log($logger)
bc4e95
 
bc4e95
 capabilities, capabilities_pcsd = get_capabilities($logger)
bc4e95
 CAPABILITIES = capabilities.freeze
bc4e95
diff --git a/pcsd/pcsd-ruby.service b/pcsd/pcsd-ruby.service
bc4e95
new file mode 100644
bc4e95
index 00000000..deefdf4f
bc4e95
--- /dev/null
bc4e95
+++ b/pcsd/pcsd-ruby.service
bc4e95
@@ -0,0 +1,20 @@
bc4e95
+[Unit]
bc4e95
+Description=PCS GUI and remote configuration interface (Ruby)
bc4e95
+Documentation=man:pcsd(8)
bc4e95
+Documentation=man:pcs(8)
bc4e95
+Requires=network-online.target
bc4e95
+After=network-online.target
bc4e95
+# Stop the service automatically if nothing that depends on it is running
bc4e95
+StopWhenUnneeded=true
bc4e95
+# When stopping or restarting pcsd, stop or restart pcsd-ruby as well
bc4e95
+PartOf=pcsd.service
bc4e95
+
bc4e95
+[Service]
bc4e95
+EnvironmentFile=/etc/sysconfig/pcsd
bc4e95
+Environment=GEM_HOME=/usr/lib/pcsd/vendor/bundle/ruby
bc4e95
+# This file holds the selinux context
bc4e95
+ExecStart=/usr/lib/pcsd/pcsd
bc4e95
+Type=notify
bc4e95
+
bc4e95
+[Install]
bc4e95
+WantedBy=multi-user.target
bc4e95
diff --git a/pcsd/pcsd.conf b/pcsd/pcsd.conf
bc4e95
index 4761c73f..a968f459 100644
bc4e95
--- a/pcsd/pcsd.conf
bc4e95
+++ b/pcsd/pcsd.conf
bc4e95
@@ -38,3 +38,7 @@ PCSD_SESSION_LIFETIME=3600
bc4e95
 #HTTPS_PROXY=
bc4e95
 # Do not use proxy for specified hostnames
bc4e95
 #NO_PROXY=
bc4e95
+
bc4e95
+
bc4e95
+# Do not change
bc4e95
+RACK_ENV=production
bc4e95
diff --git a/pcsd/pcsd.rb b/pcsd/pcsd.rb
bc4e95
index eff5c9a8..4cb98799 100644
bc4e95
--- a/pcsd/pcsd.rb
bc4e95
+++ b/pcsd/pcsd.rb
bc4e95
@@ -22,6 +22,7 @@ require 'permissions.rb'
bc4e95
 use Rack::CommonLogger
bc4e95
 
bc4e95
 set :app_file, __FILE__
bc4e95
+set :logging, false
bc4e95
 
bc4e95
 def __msg_cluster_name_already_used(cluster_name)
bc4e95
   return "The cluster name '#{cluster_name}' has already been added. You may not add two clusters with the same name."
bc4e95
@@ -44,17 +45,17 @@ end
bc4e95
 
bc4e95
 def getAuthUser()
bc4e95
   return {
bc4e95
-    :username => $tornado_username,
bc4e95
-    :usergroups => $tornado_groups,
bc4e95
+    :username => Thread.current[:tornado_username],
bc4e95
+    :usergroups => Thread.current[:tornado_groups],
bc4e95
   }
bc4e95
 end
bc4e95
 
bc4e95
 before do
bc4e95
   # nobody is logged in yet
bc4e95
   @auth_user = nil
bc4e95
-  @tornado_session_username = $tornado_username
bc4e95
-  @tornado_session_groups = $tornado_groups
bc4e95
-  @tornado_is_authenticated = $tornado_is_authenticated
bc4e95
+  @tornado_session_username = Thread.current[:tornado_username]
bc4e95
+  @tornado_session_groups = Thread.current[:tornado_groups]
bc4e95
+  @tornado_is_authenticated = Thread.current[:tornado_is_authenticated]
bc4e95
 
bc4e95
   if(request.path.start_with?('/remote/') and request.path != "/remote/auth") or request.path == '/run_pcs'
bc4e95
     # Sets @auth_user to a hash containing info about logged in user or halts
bc4e95
@@ -71,18 +72,8 @@ end
bc4e95
 configure do
bc4e95
   PCS = get_pcs_path()
bc4e95
   PCS_INTERNAL = get_pcs_internal_path()
bc4e95
-  $logger = configure_logger(StringIO.new())
bc4e95
-  $logger.formatter = proc {|severity, datetime, progname, msg|
bc4e95
-    # rushing a raw logging info into the global
bc4e95
-    $tornado_logs << {
bc4e95
-      :level => severity,
bc4e95
-      :timestamp_usec => (datetime.to_f * 1000000).to_i,
bc4e95
-      :message => msg,
bc4e95
-    }
bc4e95
-    # don't need any log to the stream
bc4e95
-    ""
bc4e95
-  }
bc4e95
-
bc4e95
+  $logger = configure_logger()
bc4e95
+  early_log($logger)
bc4e95
   capabilities, capabilities_pcsd = get_capabilities($logger)
bc4e95
   CAPABILITIES = capabilities.freeze
bc4e95
   CAPABILITIES_PCSD = capabilities_pcsd.freeze
bc4e95
@@ -599,7 +590,8 @@ get '/manage/get_nodes_sw_versions' do
bc4e95
     nodes = params[:node_list]
bc4e95
   end
bc4e95
   nodes.each {|node|
bc4e95
-    threads << Thread.new {
bc4e95
+    threads << Thread.new(Thread.current[:pcsd_logger_container]) { |logger|
bc4e95
+      Thread.current[:pcsd_logger_container] = logger
bc4e95
       code, response = send_request_with_token(
bc4e95
         auth_user, node, 'get_sw_versions'
bc4e95
       )
bc4e95
@@ -625,7 +617,8 @@ post '/manage/auth_gui_against_nodes' do
bc4e95
 
bc4e95
   data = JSON.parse(params.fetch('data_json'))
bc4e95
   data.fetch('nodes').each { |node_name, node_data|
bc4e95
-    threads << Thread.new {
bc4e95
+    threads << Thread.new(Thread.current[:pcsd_logger_container]) { |logger|
bc4e95
+      Thread.current[:pcsd_logger_container] = logger
bc4e95
       dest_list = node_data.fetch('dest_list')
bc4e95
       addr = dest_list.fetch(0).fetch('addr')
bc4e95
       port = dest_list.fetch(0).fetch('port')
bc4e95
diff --git a/pcsd/pcsd.service b/pcsd/pcsd.service
bc4e95
index 88d237af..0cab20ef 100644
bc4e95
--- a/pcsd/pcsd.service
bc4e95
+++ b/pcsd/pcsd.service
bc4e95
@@ -4,6 +4,8 @@ Documentation=man:pcsd(8)
bc4e95
 Documentation=man:pcs(8)
bc4e95
 Requires=network-online.target
bc4e95
 After=network-online.target
bc4e95
+Requires=pcsd-ruby.service
bc4e95
+After=pcsd-ruby.service
bc4e95
 
bc4e95
 [Service]
bc4e95
 EnvironmentFile=/etc/sysconfig/pcsd
bc4e95
diff --git a/pcsd/pcsd.service-runner b/pcsd/pcsd.service-runner
bc4e95
new file mode 100644
bc4e95
index 00000000..40c401fa
bc4e95
--- /dev/null
bc4e95
+++ b/pcsd/pcsd.service-runner
bc4e95
@@ -0,0 +1,24 @@
bc4e95
+#!/usr/bin/ruby
bc4e95
+# This file is a runner for ruby part of pcsd callable from a systemd unit.
bc4e95
+# It also serves as a holder of a selinux context.
bc4e95
+
bc4e95
+begin
bc4e95
+  # add pcsd to the load path (ruby -I)
bc4e95
+  libdir = File.dirname(__FILE__)
bc4e95
+  $LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)
bc4e95
+
bc4e95
+  # change current directory (ruby -C)
bc4e95
+  Dir.chdir('/var/lib/pcsd')
bc4e95
+
bc4e95
+  # import and run ruby daemon
bc4e95
+  require 'rserver.rb'
bc4e95
+rescue SignalException => e
bc4e95
+  if [Signal.list['INT'], Signal.list['TERM']].include?(e.signo)
bc4e95
+    # gracefully exit on SIGINT and SIGTERM
bc4e95
+    # pcsd sets up signal handlers later, this catches exceptions which occur
bc4e95
+    # by recieving signals before the handlers have been set up.
bc4e95
+    exit
bc4e95
+  else
bc4e95
+    raise
bc4e95
+  end
bc4e95
+end
bc4e95
diff --git a/pcsd/remote.rb b/pcsd/remote.rb
bc4e95
index 28b91382..760d3374 100644
bc4e95
--- a/pcsd/remote.rb
bc4e95
+++ b/pcsd/remote.rb
bc4e95
@@ -938,7 +938,8 @@ def status_all(params, request, auth_user, nodes=[], dont_update_config=false)
bc4e95
   threads = []
bc4e95
   forbidden_nodes = {}
bc4e95
   nodes.each {|node|
bc4e95
-    threads << Thread.new {
bc4e95
+    threads << Thread.new(Thread.current[:pcsd_logger_container]) { |logger|
bc4e95
+      Thread.current[:pcsd_logger_container] = logger
bc4e95
       code, response = send_request_with_token(auth_user, node, 'status')
bc4e95
       if 403 == code
bc4e95
         forbidden_nodes[node] = true
bc4e95
@@ -994,7 +995,8 @@ def clusters_overview(params, request, auth_user)
bc4e95
   threads = []
bc4e95
   config = PCSConfig.new(Cfgsync::PcsdSettings.from_file().text())
bc4e95
   config.clusters.each { |cluster|
bc4e95
-    threads << Thread.new {
bc4e95
+    threads << Thread.new(Thread.current[:pcsd_logger_container]) { |logger|
bc4e95
+      Thread.current[:pcsd_logger_container] = logger
bc4e95
       cluster_map[cluster.name] = {
bc4e95
         'cluster_name' => cluster.name,
bc4e95
         'error_list' => [
bc4e95
diff --git a/pcsd/rserver.rb b/pcsd/rserver.rb
bc4e95
new file mode 100644
bc4e95
index 00000000..6002a73c
bc4e95
--- /dev/null
bc4e95
+++ b/pcsd/rserver.rb
bc4e95
@@ -0,0 +1,98 @@
bc4e95
+require "base64"
bc4e95
+require "date"
bc4e95
+require "json"
bc4e95
+require 'rack'
bc4e95
+require 'sinatra'
bc4e95
+require 'thin'
bc4e95
+
bc4e95
+require 'settings.rb'
bc4e95
+
bc4e95
+def pack_response(response)
bc4e95
+  return [200, {}, [response.to_json.to_str]]
bc4e95
+end
bc4e95
+
bc4e95
+def unpack_request(transport_env)
bc4e95
+  return JSON.parse(Base64.strict_decode64(
bc4e95
+    transport_env["rack.request.form_hash"]["TORNADO_REQUEST"]
bc4e95
+  ))
bc4e95
+end
bc4e95
+
bc4e95
+class TornadoCommunicationMiddleware
bc4e95
+  def initialize(app)
bc4e95
+    @app = app
bc4e95
+  end
bc4e95
+
bc4e95
+  def call(transport_env)
bc4e95
+    Thread.current[:pcsd_logger_container] = []
bc4e95
+    begin
bc4e95
+      request = unpack_request(transport_env)
bc4e95
+
bc4e95
+      if ["sinatra_gui", "sinatra_remote"].include?(request["type"])
bc4e95
+        if request["type"] == "sinatra_gui"
bc4e95
+          session = request["session"]
bc4e95
+          Thread.current[:tornado_username] = session["username"]
bc4e95
+          Thread.current[:tornado_groups] = session["groups"]
bc4e95
+          Thread.current[:tornado_is_authenticated] = session["is_authenticated"]
bc4e95
+        end
bc4e95
+
bc4e95
+        # Keys rack.input and rack.errors are required. We make sure they are
bc4e95
+        # there.
bc4e95
+        request_env = request["env"]
bc4e95
+        request_env["rack.input"] = StringIO.new(request_env["rack.input"])
bc4e95
+        request_env["rack.errors"] = StringIO.new()
bc4e95
+
bc4e95
+        status, headers, body = @app.call(request_env)
bc4e95
+
bc4e95
+        rack_errors = request_env['rack.errors'].string()
bc4e95
+        if not rack_errors.empty?()
bc4e95
+          $logger.error(rack_errors)
bc4e95
+        end
bc4e95
+
bc4e95
+        return pack_response({
bc4e95
+          :status => status,
bc4e95
+          :headers => headers,
bc4e95
+          :body => Base64.encode64(body.join("")),
bc4e95
+          :logs => Thread.current[:pcsd_logger_container],
bc4e95
+        })
bc4e95
+      end
bc4e95
+
bc4e95
+      if request["type"] == "sync_configs"
bc4e95
+        return pack_response({
bc4e95
+          :next => Time.now.to_i + run_cfgsync(),
bc4e95
+          :logs => Thread.current[:pcsd_logger_container],
bc4e95
+        })
bc4e95
+      end
bc4e95
+
bc4e95
+      raise "Unexpected value for key 'type': '#{request['type']}'"
bc4e95
+    rescue => e
bc4e95
+      return pack_response({:error => "Processing request error: '#{e}'"})
bc4e95
+    end
bc4e95
+  end
bc4e95
+end
bc4e95
+
bc4e95
+
bc4e95
+use TornadoCommunicationMiddleware
bc4e95
+
bc4e95
+require 'pcsd'
bc4e95
+
bc4e95
+::Rack::Handler.get('thin').run(Sinatra::Application, {
bc4e95
+  :Host => PCSD_RUBY_SOCKET,
bc4e95
+}) do |server|
bc4e95
+  puts server.class
bc4e95
+  server.threaded = true
bc4e95
+  # notify systemd we are running
bc4e95
+  if ISSYSTEMCTL
bc4e95
+    if ENV['NOTIFY_SOCKET']
bc4e95
+      socket_name = ENV['NOTIFY_SOCKET'].dup
bc4e95
+      if socket_name.start_with?('@')
bc4e95
+        # abstract namespace socket
bc4e95
+        socket_name[0] = "\0"
bc4e95
+      end
bc4e95
+      $logger.info("Notifying systemd we are running (socket #{socket_name})")
bc4e95
+      sd_socket = Socket.new(Socket::AF_UNIX, Socket::SOCK_DGRAM)
bc4e95
+      sd_socket.connect(Socket.pack_sockaddr_un(socket_name))
bc4e95
+      sd_socket.send('READY=1', 0)
bc4e95
+      sd_socket.close()
bc4e95
+    end
bc4e95
+  end
bc4e95
+end
bc4e95
diff --git a/pcsd/settings.rb b/pcsd/settings.rb
bc4e95
index e8dc0c96..4caa5b4c 100644
bc4e95
--- a/pcsd/settings.rb
bc4e95
+++ b/pcsd/settings.rb
bc4e95
@@ -3,6 +3,7 @@ PCS_INTERNAL_EXEC = '/usr/lib/pcs/pcs_internal'
bc4e95
 PCSD_EXEC_LOCATION = '/usr/lib/pcsd/'
bc4e95
 PCSD_VAR_LOCATION = '/var/lib/pcsd/'
bc4e95
 PCSD_DEFAULT_PORT = 2224
bc4e95
+PCSD_RUBY_SOCKET = '/run/pcsd-ruby.socket'
bc4e95
 
bc4e95
 CRT_FILE = PCSD_VAR_LOCATION + 'pcsd.crt'
bc4e95
 KEY_FILE = PCSD_VAR_LOCATION + 'pcsd.key'
bc4e95
diff --git a/pcsd/settings.rb.debian b/pcsd/settings.rb.debian
bc4e95
index daaae37b..c547bc51 100644
bc4e95
--- a/pcsd/settings.rb.debian
bc4e95
+++ b/pcsd/settings.rb.debian
bc4e95
@@ -3,6 +3,7 @@ PCS_INTERNAL_EXEC = '/usr/lib/pcs/pcs_internal'
bc4e95
 PCSD_EXEC_LOCATION = '/usr/share/pcsd/'
bc4e95
 PCSD_VAR_LOCATION = '/var/lib/pcsd/'
bc4e95
 PCSD_DEFAULT_PORT = 2224
bc4e95
+PCSD_RUBY_SOCKET = '/run/pcsd-ruby.socket'
bc4e95
 
bc4e95
 CRT_FILE = PCSD_VAR_LOCATION + 'pcsd.crt'
bc4e95
 KEY_FILE = PCSD_VAR_LOCATION + 'pcsd.key'
bc4e95
diff --git a/pcsd/sinatra_cmdline_wrapper.rb b/pcsd/sinatra_cmdline_wrapper.rb
bc4e95
deleted file mode 100644
bc4e95
index f7b22008..00000000
bc4e95
--- a/pcsd/sinatra_cmdline_wrapper.rb
bc4e95
+++ /dev/null
bc4e95
@@ -1,63 +0,0 @@
bc4e95
-require "base64"
bc4e95
-require "date"
bc4e95
-require "json"
bc4e95
-
bc4e95
-request_json = ARGF.read()
bc4e95
-
bc4e95
-begin
bc4e95
-  request = JSON.parse(request_json)
bc4e95
-rescue => e
bc4e95
-  puts e
bc4e95
-  exit
bc4e95
-end
bc4e95
-
bc4e95
-if !request.include?("type")
bc4e95
-  result = {:error => "Type not specified"}
bc4e95
-  print result.to_json
bc4e95
-  exit
bc4e95
-end
bc4e95
-
bc4e95
-$tornado_logs = []
bc4e95
-
bc4e95
-require 'pcsd'
bc4e95
-
bc4e95
-if ["sinatra_gui", "sinatra_remote"].include?(request["type"])
bc4e95
-  if request["type"] == "sinatra_gui"
bc4e95
-    $tornado_username = request["session"]["username"]
bc4e95
-    $tornado_groups = request["session"]["groups"]
bc4e95
-    $tornado_is_authenticated = request["session"]["is_authenticated"]
bc4e95
-  end
bc4e95
-
bc4e95
-  set :logging, true
bc4e95
-  set :run, false
bc4e95
-  # Do not turn exceptions into fancy 100kB HTML pages and print them on stdout.
bc4e95
-  # Instead, rack.errors is logged and therefore returned in result[:log].
bc4e95
-  set :show_exceptions, false
bc4e95
-  app = [Sinatra::Application][0]
bc4e95
-
bc4e95
-  env = request["env"]
bc4e95
-  env["rack.input"] = StringIO.new(env["rack.input"])
bc4e95
-  env["rack.errors"] = StringIO.new()
bc4e95
-
bc4e95
-  status, headers, body = app.call(env)
bc4e95
-  rack_errors = env['rack.errors'].string()
bc4e95
-  if not rack_errors.empty?()
bc4e95
-    $logger.error(rack_errors)
bc4e95
-  end
bc4e95
-
bc4e95
-  result = {
bc4e95
-    :status => status,
bc4e95
-    :headers => headers,
bc4e95
-    :body => Base64.encode64(body.join("")),
bc4e95
-  }
bc4e95
-
bc4e95
-elsif request["type"] == "sync_configs"
bc4e95
-  result = {
bc4e95
-    :next => Time.now.to_i + run_cfgsync()
bc4e95
-  }
bc4e95
-else
bc4e95
-  result = {:error => "Unknown type: '#{request["type"]}'"}
bc4e95
-end
bc4e95
-
bc4e95
-result[:logs] = $tornado_logs
bc4e95
-print result.to_json
bc4e95
-- 
bc4e95
2.21.1
bc4e95