|
|
bc4e95 |
From 770252b476bc342ea08da2bc5b83de713463d14a Mon Sep 17 00:00:00 2001
|
|
|
bc4e95 |
From: Ivan Devat <idevat@redhat.com>
|
|
|
bc4e95 |
Date: Thu, 12 Mar 2020 15:32:31 +0100
|
|
|
bc4e95 |
Subject: [PATCH 1/2] send request from python to ruby more directly
|
|
|
bc4e95 |
|
|
|
bc4e95 |
Rack protection middleware is launched before
|
|
|
bc4e95 |
TornadoCommunicationMiddleware. When request parts are unpacked in
|
|
|
bc4e95 |
TornadoCommunicationMiddleware they are not checked by rack protection.
|
|
|
bc4e95 |
|
|
|
bc4e95 |
This commit changes communication between python and ruby - request is
|
|
|
bc4e95 |
sent to ruby more directly (without need to unpack request in sinatra
|
|
|
bc4e95 |
middleware).
|
|
|
bc4e95 |
---
|
|
|
bc4e95 |
pcs/daemon/ruby_pcsd.py | 217 ++++++++++++++--------
|
|
|
bc4e95 |
pcs_test/tier0/daemon/app/fixtures_app.py | 7 +-
|
|
|
bc4e95 |
pcs_test/tier0/daemon/test_ruby_pcsd.py | 61 ++----
|
|
|
bc4e95 |
pcsd/rserver.rb | 39 ++--
|
|
|
bc4e95 |
4 files changed, 175 insertions(+), 149 deletions(-)
|
|
|
bc4e95 |
|
|
|
bc4e95 |
diff --git a/pcs/daemon/ruby_pcsd.py b/pcs/daemon/ruby_pcsd.py
|
|
|
bc4e95 |
index e612f8da..53c53eaf 100644
|
|
|
bc4e95 |
--- a/pcs/daemon/ruby_pcsd.py
|
|
|
bc4e95 |
+++ b/pcs/daemon/ruby_pcsd.py
|
|
|
bc4e95 |
@@ -7,8 +7,8 @@ from time import time as now
|
|
|
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.httpclient import AsyncHTTPClient
|
|
|
bc4e95 |
+from tornado.httputil import HTTPServerRequest, HTTPHeaders
|
|
|
bc4e95 |
+from tornado.httpclient import AsyncHTTPClient, HTTPClientError
|
|
|
bc4e95 |
from tornado.curl_httpclient import CurlError
|
|
|
bc4e95 |
|
|
|
bc4e95 |
|
|
|
bc4e95 |
@@ -29,6 +29,11 @@ RUBY_LOG_LEVEL_MAP = {
|
|
|
bc4e95 |
"DEBUG": logging.DEBUG,
|
|
|
bc4e95 |
}
|
|
|
bc4e95 |
|
|
|
bc4e95 |
+__id_dict = {"id": 0}
|
|
|
bc4e95 |
+def get_request_id():
|
|
|
bc4e95 |
+ __id_dict["id"] += 1
|
|
|
bc4e95 |
+ return __id_dict["id"]
|
|
|
bc4e95 |
+
|
|
|
bc4e95 |
class SinatraResult(namedtuple("SinatraResult", "headers, status, body")):
|
|
|
bc4e95 |
@classmethod
|
|
|
bc4e95 |
def from_response(cls, response):
|
|
|
bc4e95 |
@@ -60,6 +65,59 @@ def process_response_logs(rb_log_list):
|
|
|
bc4e95 |
group_id=group_id
|
|
|
bc4e95 |
)
|
|
|
bc4e95 |
|
|
|
bc4e95 |
+class RubyDaemonRequest(namedtuple(
|
|
|
bc4e95 |
+ "RubyDaemonRequest",
|
|
|
bc4e95 |
+ "request_type, path, query, headers, method, body"
|
|
|
bc4e95 |
+)):
|
|
|
bc4e95 |
+ def __new__(
|
|
|
bc4e95 |
+ cls,
|
|
|
bc4e95 |
+ request_type,
|
|
|
bc4e95 |
+ http_request: HTTPServerRequest = None,
|
|
|
bc4e95 |
+ payload=None,
|
|
|
bc4e95 |
+ ):
|
|
|
bc4e95 |
+ headers = http_request.headers if http_request else HTTPHeaders()
|
|
|
bc4e95 |
+ headers.add("X-Pcsd-Type", request_type)
|
|
|
bc4e95 |
+ if payload:
|
|
|
bc4e95 |
+ headers.add(
|
|
|
bc4e95 |
+ "X-Pcsd-Payload",
|
|
|
bc4e95 |
+ b64encode(json.dumps(payload).encode()).decode()
|
|
|
bc4e95 |
+ )
|
|
|
bc4e95 |
+ return super(RubyDaemonRequest, cls).__new__(
|
|
|
bc4e95 |
+ cls,
|
|
|
bc4e95 |
+ request_type,
|
|
|
bc4e95 |
+ http_request.path if http_request else "",
|
|
|
bc4e95 |
+ http_request.query if http_request else "",
|
|
|
bc4e95 |
+ headers,
|
|
|
bc4e95 |
+ http_request.method if http_request else "GET",
|
|
|
bc4e95 |
+ http_request.body if http_request else None,
|
|
|
bc4e95 |
+ )
|
|
|
bc4e95 |
+
|
|
|
bc4e95 |
+ @property
|
|
|
bc4e95 |
+ def url(self):
|
|
|
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 |
+ query = f"?{self.query}" if self.query else ""
|
|
|
bc4e95 |
+ return f"localhost/{self.path}{query}"
|
|
|
bc4e95 |
+
|
|
|
bc4e95 |
+ @property
|
|
|
bc4e95 |
+ def is_get(self):
|
|
|
bc4e95 |
+ return self.method.upper() == "GET"
|
|
|
bc4e95 |
+
|
|
|
bc4e95 |
+ @property
|
|
|
bc4e95 |
+ def has_http_request_detail(self):
|
|
|
bc4e95 |
+ return self.path or self.query or self.method != "GET" or self.body
|
|
|
bc4e95 |
+
|
|
|
bc4e95 |
+def log_ruby_daemon_request(label, request: RubyDaemonRequest):
|
|
|
bc4e95 |
+ log.pcsd.debug("%s type: '%s'", label, request.request_type)
|
|
|
bc4e95 |
+ if request.has_http_request_detail:
|
|
|
bc4e95 |
+ log.pcsd.debug("%s path: '%s'", label, request.path)
|
|
|
bc4e95 |
+ if request.query:
|
|
|
bc4e95 |
+ log.pcsd.debug("%s query: '%s'", label, request.query)
|
|
|
bc4e95 |
+ log.pcsd.debug("%s method: '%s'", label, request.method)
|
|
|
bc4e95 |
+ if request.body:
|
|
|
bc4e95 |
+ log.pcsd.debug("%s body: '%s'", label, request.body)
|
|
|
bc4e95 |
+
|
|
|
bc4e95 |
class Wrapper:
|
|
|
bc4e95 |
def __init__(self, pcsd_ruby_socket, debug=False):
|
|
|
bc4e95 |
self.__debug = debug
|
|
|
bc4e95 |
@@ -67,74 +125,87 @@ class Wrapper:
|
|
|
bc4e95 |
self.__client = AsyncHTTPClient()
|
|
|
bc4e95 |
self.__pcsd_ruby_socket = pcsd_ruby_socket
|
|
|
bc4e95 |
|
|
|
bc4e95 |
- @staticmethod
|
|
|
bc4e95 |
- def get_sinatra_request(request: HTTPServerRequest):
|
|
|
bc4e95 |
- host, port = split_host_and_port(request.host)
|
|
|
bc4e95 |
- return {"env": {
|
|
|
bc4e95 |
- "PATH_INFO": request.path,
|
|
|
bc4e95 |
- "QUERY_STRING": request.query,
|
|
|
bc4e95 |
- "REMOTE_ADDR": request.remote_ip,
|
|
|
bc4e95 |
- "REMOTE_HOST": request.host,
|
|
|
bc4e95 |
- "REQUEST_METHOD": request.method,
|
|
|
bc4e95 |
- "REQUEST_URI": f"{request.protocol}://{request.host}{request.uri}",
|
|
|
bc4e95 |
- "SCRIPT_NAME": "",
|
|
|
bc4e95 |
- "SERVER_NAME": host,
|
|
|
bc4e95 |
- "SERVER_PORT": port,
|
|
|
bc4e95 |
- "SERVER_PROTOCOL": request.version,
|
|
|
bc4e95 |
- "HTTP_HOST": request.host,
|
|
|
bc4e95 |
- "HTTP_ACCEPT": "*/*",
|
|
|
bc4e95 |
- "HTTP_COOKIE": ";".join([
|
|
|
bc4e95 |
- v.OutputString() for v in request.cookies.values()
|
|
|
bc4e95 |
- ]),
|
|
|
bc4e95 |
- "HTTPS": "on" if request.protocol == "https" else "off",
|
|
|
bc4e95 |
- "HTTP_VERSION": request.version,
|
|
|
bc4e95 |
- "REQUEST_PATH": request.path,
|
|
|
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 |
- # 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 |
-
|
|
|
bc4e95 |
- if self.__debug:
|
|
|
bc4e95 |
- log.pcsd.debug("Ruby daemon request: '%s'", request_json)
|
|
|
bc4e95 |
+ async def send_to_ruby(self, request: RubyDaemonRequest):
|
|
|
bc4e95 |
try:
|
|
|
bc4e95 |
- ruby_response = await self.send_to_ruby(request_json)
|
|
|
bc4e95 |
+ return (await self.__client.fetch(
|
|
|
bc4e95 |
+ request.url,
|
|
|
bc4e95 |
+ headers=request.headers,
|
|
|
bc4e95 |
+ method=request.method,
|
|
|
bc4e95 |
+ # Tornado enforces body=None for GET method:
|
|
|
bc4e95 |
+ # Even with `allow_nonstandard_methods` we disallow GET with a
|
|
|
bc4e95 |
+ # body (because libcurl doesn't allow it unless we use
|
|
|
bc4e95 |
+ # CUSTOMREQUEST). While the spec doesn't forbid clients from
|
|
|
bc4e95 |
+ # sending a body, it arguably disallows the server from doing
|
|
|
bc4e95 |
+ # anything with them.
|
|
|
bc4e95 |
+ body=(request.body if not request.is_get else None),
|
|
|
bc4e95 |
+ prepare_curl_callback=self.prepare_curl_callback,
|
|
|
bc4e95 |
+ )).body
|
|
|
bc4e95 |
except CurlError as e:
|
|
|
bc4e95 |
+ # This error we can get e.g. when ruby daemon is down.
|
|
|
bc4e95 |
log.pcsd.error(
|
|
|
bc4e95 |
"Cannot connect to ruby daemon (message: '%s'). Is it running?",
|
|
|
bc4e95 |
e
|
|
|
bc4e95 |
)
|
|
|
bc4e95 |
raise HTTPError(500)
|
|
|
bc4e95 |
+ except HTTPClientError as e:
|
|
|
bc4e95 |
+ # This error we can get e.g. when rack protection raises exception.
|
|
|
bc4e95 |
+ log.pcsd.error(
|
|
|
bc4e95 |
+ (
|
|
|
bc4e95 |
+ "Got error from ruby daemon (message: '%s')."
|
|
|
bc4e95 |
+ " Try checking system logs (e.g. journal, systemctl status"
|
|
|
bc4e95 |
+ " pcsd.service) for more information.."
|
|
|
bc4e95 |
+ ),
|
|
|
bc4e95 |
+ e
|
|
|
bc4e95 |
+ )
|
|
|
bc4e95 |
+ raise HTTPError(500)
|
|
|
bc4e95 |
+
|
|
|
bc4e95 |
+ async def run_ruby(
|
|
|
bc4e95 |
+ self,
|
|
|
bc4e95 |
+ request_type,
|
|
|
bc4e95 |
+ http_request: HTTPServerRequest = None,
|
|
|
bc4e95 |
+ payload=None,
|
|
|
bc4e95 |
+ ):
|
|
|
bc4e95 |
+ request = RubyDaemonRequest(request_type, http_request, payload)
|
|
|
bc4e95 |
+ request_id = get_request_id()
|
|
|
bc4e95 |
+
|
|
|
bc4e95 |
+ def log_request():
|
|
|
bc4e95 |
+ log_ruby_daemon_request(
|
|
|
bc4e95 |
+ f"Ruby daemon request (id: {request_id})",
|
|
|
bc4e95 |
+ request,
|
|
|
bc4e95 |
+ )
|
|
|
bc4e95 |
+
|
|
|
bc4e95 |
+ if self.__debug:
|
|
|
bc4e95 |
+ log_request()
|
|
|
bc4e95 |
+
|
|
|
bc4e95 |
+ return self.process_ruby_response(
|
|
|
bc4e95 |
+ f"Ruby daemon response (id: {request_id})",
|
|
|
bc4e95 |
+ log_request,
|
|
|
bc4e95 |
+ await self.send_to_ruby(request),
|
|
|
bc4e95 |
+ )
|
|
|
bc4e95 |
+
|
|
|
bc4e95 |
+ def process_ruby_response(self, label, log_request, ruby_response):
|
|
|
bc4e95 |
+ """
|
|
|
bc4e95 |
+ Return relevant part of unpacked ruby response. As a side effect
|
|
|
bc4e95 |
+ relevant logs are writen.
|
|
|
bc4e95 |
|
|
|
bc4e95 |
+ string label -- is used as a log prefix
|
|
|
bc4e95 |
+ callable log_request -- is used to log request when some errors happen;
|
|
|
bc4e95 |
+ we want to log request before error even if there is not debug mode
|
|
|
bc4e95 |
+ string ruby_response -- body of response from ruby; it should contain
|
|
|
bc4e95 |
+ json with dictionary with response specific keys
|
|
|
bc4e95 |
+ """
|
|
|
bc4e95 |
try:
|
|
|
bc4e95 |
response = json.loads(ruby_response)
|
|
|
bc4e95 |
if "error" in response:
|
|
|
bc4e95 |
+ if not self.__debug:
|
|
|
bc4e95 |
+ log_request()
|
|
|
bc4e95 |
log.pcsd.error(
|
|
|
bc4e95 |
- "Ruby daemon response contains an error: '%s'",
|
|
|
bc4e95 |
+ "%s contains an error: '%s'",
|
|
|
bc4e95 |
+ label,
|
|
|
bc4e95 |
json.dumps(response)
|
|
|
bc4e95 |
)
|
|
|
bc4e95 |
raise HTTPError(500)
|
|
|
bc4e95 |
@@ -144,56 +215,52 @@ class Wrapper:
|
|
|
bc4e95 |
body = b64decode(response.pop("body"))
|
|
|
bc4e95 |
if self.__debug:
|
|
|
bc4e95 |
log.pcsd.debug(
|
|
|
bc4e95 |
- "Ruby daemon response (without logs and body): '%s'",
|
|
|
bc4e95 |
+ "%s (without logs and body): '%s'",
|
|
|
bc4e95 |
+ label,
|
|
|
bc4e95 |
json.dumps(response)
|
|
|
bc4e95 |
)
|
|
|
bc4e95 |
- log.pcsd.debug("Ruby daemon response body: '%s'", body)
|
|
|
bc4e95 |
+ log.pcsd.debug("%s body: '%s'", label, body)
|
|
|
bc4e95 |
response["body"] = body
|
|
|
bc4e95 |
|
|
|
bc4e95 |
elif self.__debug:
|
|
|
bc4e95 |
log.pcsd.debug(
|
|
|
bc4e95 |
- "Ruby daemon response (without logs): '%s'",
|
|
|
bc4e95 |
+ "%s (without logs): '%s'",
|
|
|
bc4e95 |
+ label,
|
|
|
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.debug("%s: '%s'", label, ruby_response)
|
|
|
bc4e95 |
+ else:
|
|
|
bc4e95 |
+ log_request()
|
|
|
bc4e95 |
+
|
|
|
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 |
) -> SinatraResult:
|
|
|
bc4e95 |
- sinatra_request = self.get_sinatra_request(request)
|
|
|
bc4e95 |
# Sessions handling was removed from ruby. However, some session
|
|
|
bc4e95 |
# information is needed for ruby code (e.g. rendering some parts of
|
|
|
bc4e95 |
# templates). So this information must be sent to ruby by another way.
|
|
|
bc4e95 |
- sinatra_request.update({
|
|
|
bc4e95 |
- "session": {
|
|
|
bc4e95 |
+ return SinatraResult.from_response(
|
|
|
bc4e95 |
+ await convert_yielded(self.run_ruby(SINATRA_GUI, request, {
|
|
|
bc4e95 |
"username": user,
|
|
|
bc4e95 |
"groups": groups,
|
|
|
bc4e95 |
"is_authenticated": is_authenticated,
|
|
|
bc4e95 |
- }
|
|
|
bc4e95 |
- })
|
|
|
bc4e95 |
- response = await convert_yielded(self.run_ruby(
|
|
|
bc4e95 |
- SINATRA_GUI,
|
|
|
bc4e95 |
- sinatra_request
|
|
|
bc4e95 |
- ))
|
|
|
bc4e95 |
- return SinatraResult.from_response(response)
|
|
|
bc4e95 |
+ }))
|
|
|
bc4e95 |
+ )
|
|
|
bc4e95 |
|
|
|
bc4e95 |
async def request_remote(self, request: HTTPServerRequest) -> SinatraResult:
|
|
|
bc4e95 |
- response = await convert_yielded(self.run_ruby(
|
|
|
bc4e95 |
- SINATRA_REMOTE,
|
|
|
bc4e95 |
- self.get_sinatra_request(request)
|
|
|
bc4e95 |
- ))
|
|
|
bc4e95 |
- return SinatraResult.from_response(response)
|
|
|
bc4e95 |
+ return SinatraResult.from_response(
|
|
|
bc4e95 |
+ await convert_yielded(self.run_ruby(SINATRA_REMOTE, request))
|
|
|
bc4e95 |
+ )
|
|
|
bc4e95 |
|
|
|
bc4e95 |
async def sync_configs(self):
|
|
|
bc4e95 |
try:
|
|
|
bc4e95 |
- response = await convert_yielded(self.run_ruby(SYNC_CONFIGS))
|
|
|
bc4e95 |
- return response["next"]
|
|
|
bc4e95 |
+ return (await convert_yielded(self.run_ruby(SYNC_CONFIGS)))["next"]
|
|
|
bc4e95 |
except HTTPError:
|
|
|
bc4e95 |
log.pcsd.error("Config synchronization failed")
|
|
|
bc4e95 |
return int(now()) + DEFAULT_SYNC_CONFIG_DELAY
|
|
|
bc4e95 |
diff --git a/pcs_test/tier0/daemon/app/fixtures_app.py b/pcs_test/tier0/daemon/app/fixtures_app.py
|
|
|
bc4e95 |
index 8d5b8f4c..590203b4 100644
|
|
|
bc4e95 |
--- a/pcs_test/tier0/daemon/app/fixtures_app.py
|
|
|
bc4e95 |
+++ b/pcs_test/tier0/daemon/app/fixtures_app.py
|
|
|
bc4e95 |
@@ -20,7 +20,12 @@ class RubyPcsdWrapper(ruby_pcsd.Wrapper):
|
|
|
bc4e95 |
self.headers = {"Some": "value"}
|
|
|
bc4e95 |
self.body = b"Success action"
|
|
|
bc4e95 |
|
|
|
bc4e95 |
- async def run_ruby(self, request_type, request=None):
|
|
|
bc4e95 |
+ async def run_ruby(
|
|
|
bc4e95 |
+ self,
|
|
|
bc4e95 |
+ request_type,
|
|
|
bc4e95 |
+ http_request=None,
|
|
|
bc4e95 |
+ payload=None,
|
|
|
bc4e95 |
+ ):
|
|
|
bc4e95 |
if request_type != self.request_type:
|
|
|
bc4e95 |
raise AssertionError(
|
|
|
bc4e95 |
f"Wrong request type: expected '{self.request_type}'"
|
|
|
bc4e95 |
diff --git a/pcs_test/tier0/daemon/test_ruby_pcsd.py b/pcs_test/tier0/daemon/test_ruby_pcsd.py
|
|
|
bc4e95 |
index 28f14c87..32eb74cc 100644
|
|
|
bc4e95 |
--- a/pcs_test/tier0/daemon/test_ruby_pcsd.py
|
|
|
bc4e95 |
+++ b/pcs_test/tier0/daemon/test_ruby_pcsd.py
|
|
|
bc4e95 |
@@ -4,7 +4,7 @@ from base64 import b64encode
|
|
|
bc4e95 |
from unittest import TestCase, mock
|
|
|
bc4e95 |
from urllib.parse import urlencode
|
|
|
bc4e95 |
|
|
|
bc4e95 |
-from tornado.httputil import HTTPServerRequest
|
|
|
bc4e95 |
+from tornado.httputil import HTTPServerRequest, HTTPHeaders
|
|
|
bc4e95 |
from tornado.testing import AsyncTestCase, gen_test
|
|
|
bc4e95 |
from tornado.web import HTTPError
|
|
|
bc4e95 |
|
|
|
bc4e95 |
@@ -22,46 +22,17 @@ def create_http_request():
|
|
|
bc4e95 |
return HTTPServerRequest(
|
|
|
bc4e95 |
method="POST",
|
|
|
bc4e95 |
uri="/pcsd/uri",
|
|
|
bc4e95 |
- headers={"Cookie": "cookie1=first;cookie2=second"},
|
|
|
bc4e95 |
+ headers=HTTPHeaders({"Cookie": "cookie1=first;cookie2=second"}),
|
|
|
bc4e95 |
body=str.encode(urlencode({"post-key": "post-value"})),
|
|
|
bc4e95 |
host="pcsd-host:2224"
|
|
|
bc4e95 |
)
|
|
|
bc4e95 |
|
|
|
bc4e95 |
-class GetSinatraRequest(TestCase):
|
|
|
bc4e95 |
- def test_translate_request(self):
|
|
|
bc4e95 |
- # pylint: disable=invalid-name
|
|
|
bc4e95 |
- self.maxDiff = None
|
|
|
bc4e95 |
- self.assertEqual(
|
|
|
bc4e95 |
- create_wrapper().get_sinatra_request(create_http_request()),
|
|
|
bc4e95 |
- {
|
|
|
bc4e95 |
- 'env': {
|
|
|
bc4e95 |
- 'HTTPS': 'off',
|
|
|
bc4e95 |
- 'HTTP_ACCEPT': '*/*',
|
|
|
bc4e95 |
- 'HTTP_COOKIE': 'cookie1=first;cookie2=second',
|
|
|
bc4e95 |
- 'HTTP_HOST': 'pcsd-host:2224',
|
|
|
bc4e95 |
- 'HTTP_VERSION': 'HTTP/1.0',
|
|
|
bc4e95 |
- 'PATH_INFO': '/pcsd/uri',
|
|
|
bc4e95 |
- 'QUERY_STRING': '',
|
|
|
bc4e95 |
- 'REMOTE_ADDR': None, # It requires complicated request args
|
|
|
bc4e95 |
- 'REMOTE_HOST': 'pcsd-host:2224',
|
|
|
bc4e95 |
- 'REQUEST_METHOD': 'POST',
|
|
|
bc4e95 |
- 'REQUEST_PATH': '/pcsd/uri',
|
|
|
bc4e95 |
- 'REQUEST_URI': 'http://pcsd-host:2224/pcsd/uri',
|
|
|
bc4e95 |
- 'SCRIPT_NAME': '',
|
|
|
bc4e95 |
- 'SERVER_NAME': 'pcsd-host',
|
|
|
bc4e95 |
- 'SERVER_PORT': 2224,
|
|
|
bc4e95 |
- 'SERVER_PROTOCOL': 'HTTP/1.0',
|
|
|
bc4e95 |
- 'rack.input': 'post-key=post-value'
|
|
|
bc4e95 |
- }
|
|
|
bc4e95 |
- }
|
|
|
bc4e95 |
- )
|
|
|
bc4e95 |
-
|
|
|
bc4e95 |
patch_ruby_pcsd = create_patcher(ruby_pcsd)
|
|
|
bc4e95 |
|
|
|
bc4e95 |
class RunRuby(AsyncTestCase):
|
|
|
bc4e95 |
def setUp(self):
|
|
|
bc4e95 |
self.ruby_response = ""
|
|
|
bc4e95 |
- self.request = self.create_request()
|
|
|
bc4e95 |
+ self.request = ruby_pcsd.RubyDaemonRequest(ruby_pcsd.SYNC_CONFIGS)
|
|
|
bc4e95 |
self.wrapper = create_wrapper()
|
|
|
bc4e95 |
patcher = mock.patch.object(
|
|
|
bc4e95 |
self.wrapper,
|
|
|
bc4e95 |
@@ -72,14 +43,10 @@ class RunRuby(AsyncTestCase):
|
|
|
bc4e95 |
patcher.start()
|
|
|
bc4e95 |
super().setUp()
|
|
|
bc4e95 |
|
|
|
bc4e95 |
- async def send_to_ruby(self, request_json):
|
|
|
bc4e95 |
- self.assertEqual(json.loads(request_json), self.request)
|
|
|
bc4e95 |
+ async def send_to_ruby(self, ruby_request):
|
|
|
bc4e95 |
+ self.assertEqual(ruby_request, self.request)
|
|
|
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.ruby_response = json.dumps({**run_result, "logs": []})
|
|
|
bc4e95 |
|
|
|
bc4e95 |
@@ -125,10 +92,10 @@ class RunRuby(AsyncTestCase):
|
|
|
bc4e95 |
"body": b64encode(str.encode(body)).decode(),
|
|
|
bc4e95 |
})
|
|
|
bc4e95 |
http_request = create_http_request()
|
|
|
bc4e95 |
- self.request = {
|
|
|
bc4e95 |
- **self.create_request(ruby_pcsd.SINATRA_REMOTE),
|
|
|
bc4e95 |
- **self.wrapper.get_sinatra_request(http_request),
|
|
|
bc4e95 |
- }
|
|
|
bc4e95 |
+ self.request = ruby_pcsd.RubyDaemonRequest(
|
|
|
bc4e95 |
+ ruby_pcsd.SINATRA_REMOTE,
|
|
|
bc4e95 |
+ http_request,
|
|
|
bc4e95 |
+ )
|
|
|
bc4e95 |
result = yield self.wrapper.request_remote(http_request)
|
|
|
bc4e95 |
self.assert_sinatra_result(result, headers, status, body)
|
|
|
bc4e95 |
|
|
|
bc4e95 |
@@ -148,15 +115,15 @@ class RunRuby(AsyncTestCase):
|
|
|
bc4e95 |
"body": b64encode(str.encode(body)).decode(),
|
|
|
bc4e95 |
})
|
|
|
bc4e95 |
http_request = create_http_request()
|
|
|
bc4e95 |
- self.request = {
|
|
|
bc4e95 |
- **self.create_request(ruby_pcsd.SINATRA_GUI),
|
|
|
bc4e95 |
- **self.wrapper.get_sinatra_request(http_request),
|
|
|
bc4e95 |
- "session": {
|
|
|
bc4e95 |
+ self.request = ruby_pcsd.RubyDaemonRequest(
|
|
|
bc4e95 |
+ ruby_pcsd.SINATRA_GUI,
|
|
|
bc4e95 |
+ http_request,
|
|
|
bc4e95 |
+ {
|
|
|
bc4e95 |
"username": user,
|
|
|
bc4e95 |
"groups": groups,
|
|
|
bc4e95 |
"is_authenticated": is_authenticated,
|
|
|
bc4e95 |
}
|
|
|
bc4e95 |
- }
|
|
|
bc4e95 |
+ )
|
|
|
bc4e95 |
result = yield self.wrapper.request_gui(
|
|
|
bc4e95 |
http_request,
|
|
|
bc4e95 |
user=user,
|
|
|
bc4e95 |
diff --git a/pcsd/rserver.rb b/pcsd/rserver.rb
|
|
|
bc4e95 |
index 6002a73c..4b58f252 100644
|
|
|
bc4e95 |
--- a/pcsd/rserver.rb
|
|
|
bc4e95 |
+++ b/pcsd/rserver.rb
|
|
|
bc4e95 |
@@ -11,42 +11,25 @@ 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 |
+ def call(env)
|
|
|
bc4e95 |
Thread.current[:pcsd_logger_container] = []
|
|
|
bc4e95 |
begin
|
|
|
bc4e95 |
- request = unpack_request(transport_env)
|
|
|
bc4e95 |
+ type = env["HTTP_X_PCSD_TYPE"]
|
|
|
bc4e95 |
|
|
|
bc4e95 |
- if ["sinatra_gui", "sinatra_remote"].include?(request["type"])
|
|
|
bc4e95 |
- if request["type"] == "sinatra_gui"
|
|
|
bc4e95 |
- session = request["session"]
|
|
|
bc4e95 |
+ if ["sinatra_gui", "sinatra_remote"].include?(type)
|
|
|
bc4e95 |
+ if type == "sinatra_gui"
|
|
|
bc4e95 |
+ session = JSON.parse(Base64.strict_decode64(env["HTTP_X_PCSD_PAYLOAD"]))
|
|
|
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 |
+ status, headers, body = @app.call(env)
|
|
|
bc4e95 |
|
|
|
bc4e95 |
return pack_response({
|
|
|
bc4e95 |
:status => status,
|
|
|
bc4e95 |
@@ -56,16 +39,20 @@ class TornadoCommunicationMiddleware
|
|
|
bc4e95 |
})
|
|
|
bc4e95 |
end
|
|
|
bc4e95 |
|
|
|
bc4e95 |
- if request["type"] == "sync_configs"
|
|
|
bc4e95 |
+ if 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 |
+ return pack_response({
|
|
|
bc4e95 |
+ :error => "Unexpected value for key 'type': '#{type}'"
|
|
|
bc4e95 |
+ })
|
|
|
bc4e95 |
rescue => e
|
|
|
bc4e95 |
- return pack_response({:error => "Processing request error: '#{e}'"})
|
|
|
bc4e95 |
+ return pack_response({
|
|
|
bc4e95 |
+ :error => "Processing request error: '#{e}' '#{e.backtrace}'"
|
|
|
bc4e95 |
+ })
|
|
|
bc4e95 |
end
|
|
|
bc4e95 |
end
|
|
|
bc4e95 |
end
|
|
|
bc4e95 |
--
|
|
|
bc4e95 |
2.21.1
|
|
|
bc4e95 |
|