From 2c245bbe0fc87cfa018a5da5bc7d9045edd91925 Mon Sep 17 00:00:00 2001 From: Vladislav Tsendrovskii Date: Thu, 25 Jan 2018 03:45:23 +0300 Subject: [PATCH 01/12] Add long body tests generating lua config from test Add 1k and 1M test Test responses add 1M response test --- tempesta_fw/t/functional/helpers/__init__.py | 2 +- tempesta_fw/t/functional/helpers/control.py | 8 +- tempesta_fw/t/functional/helpers/wrk.py | 57 ++++++++++++++ .../t/functional/long_body/__init__.py | 1 + .../t/functional/long_body/body_generator.py | 7 ++ .../functional/long_body/test_long_request.py | 68 +++++++++++++++++ .../long_body/test_long_response.py | 74 +++++++++++++++++++ 7 files changed, 213 insertions(+), 4 deletions(-) create mode 100644 tempesta_fw/t/functional/helpers/wrk.py create mode 100644 tempesta_fw/t/functional/long_body/__init__.py create mode 100644 tempesta_fw/t/functional/long_body/body_generator.py create mode 100644 tempesta_fw/t/functional/long_body/test_long_request.py create mode 100644 tempesta_fw/t/functional/long_body/test_long_response.py diff --git a/tempesta_fw/t/functional/helpers/__init__.py b/tempesta_fw/t/functional/helpers/__init__.py index 9bdcd2099b..be67731d0c 100644 --- a/tempesta_fw/t/functional/helpers/__init__.py +++ b/tempesta_fw/t/functional/helpers/__init__.py @@ -1,4 +1,4 @@ __all__ = ['tf_cfg', 'deproxy', 'nginx', 'tempesta', 'error', 'flacky', - 'analyzer', 'stateful', 'dmesg'] + 'analyzer', 'stateful', 'dmesg', 'wrk'] # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/tempesta_fw/t/functional/helpers/control.py b/tempesta_fw/t/functional/helpers/control.py index eecee25e75..b12290b49d 100644 --- a/tempesta_fw/t/functional/helpers/control.py +++ b/tempesta_fw/t/functional/helpers/control.py @@ -127,15 +127,17 @@ def __init__(self, threads=-1, uri='/', ssl=False): Client.__init__(self, binary='wrk', uri=uri, ssl=ssl) self.threads = threads self.script = '' + self.scriptdir = ''.join([os.path.dirname(os.path.realpath(__file__)), '/../wrk/']) - def set_script(self, script): + def set_script(self, script, scriptdir = None): self.script = script + if scriptdir != None: + self.scriptdir = scriptdir def append_script_option(self): if not self.script: return - path = ''.join([os.path.dirname(os.path.realpath(__file__)), - '/../wrk/', self.script, '.lua']) + path = ''.join([self.scriptdir, self.script, '.lua']) script_path = os.path.abspath(path) assert os.path.isfile(script_path), \ 'No script found: %s !' % script_path diff --git a/tempesta_fw/t/functional/helpers/wrk.py b/tempesta_fw/t/functional/helpers/wrk.py new file mode 100644 index 0000000000..568c946582 --- /dev/null +++ b/tempesta_fw/t/functional/helpers/wrk.py @@ -0,0 +1,57 @@ +""" Wrk script generator """ + +__author__ = 'Tempesta Technologies, Inc.' +__copyright__ = 'Copyright (C) 2017 Tempesta Technologies, Inc.' +__license__ = 'GPL2' + +import os + +class ScriptGenerator(object): + """ Generate lua script """ + request_type = "GET" + uri = "/" + headers = [] + body = "" + filename = None + def __luaencode(self, value): + return value + + def set_request_type(self, request_type): + self.request_type = request_type + + def set_uri(self, uri): + self.uri = uri + + def add_header(self, header_name, header_value): + self.headers.append((header_name, header_value)) + + def set_body(self, body): + self.body = body + + def make_config(self, filename): + """ Generate config and write it to file """ + self.filename = filename + config = open(filename, 'w') + config.write("local r = {\n") + config.write(" method = \"%s\",\n" % self.__luaencode(self.request_type)) + config.write(" path = \"%s\",\n" % self.__luaencode(self.uri)) + config.write(" headers = {\n") + for header in self.headers: + config.write(" [\"%s\"] = \"%s\",\n" % (header[0], header[1])) + config.write(" },\n") + config.write(" body = \"%s\",\n" % self.body) + config.write("}\n") + config.write("local req\n") + config.write("init = function()\n") + config.write(" req = wrk.format(r.method, r.path, r.headers, r.body)\n") + config.write("end\n") + config.write("request = function()\n") + config.write(" return req\n") + config.write("end\n") + config.close() + + def remove_config(self): + """ Remove previously created config """ + if self.filename != None: + os.unlink(self.filename) + self.filename = None diff --git a/tempesta_fw/t/functional/long_body/__init__.py b/tempesta_fw/t/functional/long_body/__init__.py new file mode 100644 index 0000000000..b056d51483 --- /dev/null +++ b/tempesta_fw/t/functional/long_body/__init__.py @@ -0,0 +1 @@ +__all__ = ['test_long_request', 'testing_long_response', 'body_generator'] diff --git a/tempesta_fw/t/functional/long_body/body_generator.py b/tempesta_fw/t/functional/long_body/body_generator.py new file mode 100644 index 0000000000..c92d61cb56 --- /dev/null +++ b/tempesta_fw/t/functional/long_body/body_generator.py @@ -0,0 +1,7 @@ +__author__ = 'Tempesta Technologies, Inc.' +__copyright__ = 'Copyright (C) 2017 Tempesta Technologies, Inc.' +__license__ = 'GPL2' + +def generate_body(length): + """ Generate body of specified length """ + return "x" * length diff --git a/tempesta_fw/t/functional/long_body/test_long_request.py b/tempesta_fw/t/functional/long_body/test_long_request.py new file mode 100644 index 0000000000..e86e27909d --- /dev/null +++ b/tempesta_fw/t/functional/long_body/test_long_request.py @@ -0,0 +1,68 @@ +""" Testing for long body in request """ + +__author__ = 'Tempesta Technologies, Inc.' +__copyright__ = 'Copyright (C) 2017 Tempesta Technologies, Inc.' +__license__ = 'GPL2' + +import unittest +import body_generator +import os + +from testers import stress +from helpers import tf_cfg, control, tempesta, remote, wrk + +class RequestTestBase(stress.StressTest): + """ Test long request """ + config = "cache 0;\n" + root = "/tmp/long_body/" + script = None + scriptdir = root + "scripts/" + wrk = None + clients = [] + generator = None + + def create_clients_with_body(self, length): + """ Create wrk client with long request body """ + self.generator = wrk.ScriptGenerator() + self.generator.set_body(body_generator.generate_body(length)) + if not os.path.exists(self.root): + os.mkdir(self.root) + elif not os.path.isdir(self.root): + raise Exception("%s already exists" % self.root) + + if not os.path.exists(self.scriptdir): + os.mkdir(self.scriptdir) + elif not os.path.isdir(self.scriptdir): + raise Exception("%s already exists" % self.scriptdir) + + self.generator.make_config(self.scriptdir + self.script + ".lua") + self.wrk = control.Wrk() + self.wrk.set_script(self.script, self.scriptdir) + self.clients = [self.wrk] + + def tearDown(self): + super(RequestTestBase, self).tearDown() + self.generator.remove_config() + os.rmdir(self.scriptdir) + +class RequestTest1k(RequestTestBase): + """ Test long request """ + script = "request_1k" + + def create_clients(self): + self.create_clients_with_body(1024) + + def test(self): + """ Test for 1kbyte body """ + self.generic_test_routine(self.config) + +class RequestTest1M(RequestTestBase): + """ Test long request """ + script = "request_1M" + + def create_clients(self): + self.create_clients_with_body(1024**2) + + def test(self): + """ Test for 1Mbyte body """ + self.generic_test_routine(self.config) diff --git a/tempesta_fw/t/functional/long_body/test_long_response.py b/tempesta_fw/t/functional/long_body/test_long_response.py new file mode 100644 index 0000000000..fb56ed51fd --- /dev/null +++ b/tempesta_fw/t/functional/long_body/test_long_response.py @@ -0,0 +1,74 @@ +""" Testing for long body in response """ + +__author__ = 'Tempesta Technologies, Inc.' +__copyright__ = 'Copyright (C) 2017 Tempesta Technologies, Inc.' +__license__ = 'GPL2' + +import os +import body_generator + +from testers import stress +from helpers import tf_cfg, control, tempesta, remote + +class ResponseTestBase(stress.StressTest): + """ Test long response """ + config = "cache 0;\n" + root = "/tmp/long_body/" + wwwdir = root + "www/" + uri = "/long.bin" + filename = wwwdir + uri + + def create_content(self, length): + """ Create content file """ + if not os.path.exists(self.root): + os.mkdir(self.root) + elif not os.path.isdir(self.root): + raise Exception("%s already exists" % self.root) + + if not os.path.exists(self.wwwdir): + os.mkdir(self.wwwdir) + elif not os.path.isdir(self.wwwdir): + raise Exception("%s already exists" % self.wwwdir) + + bfile = open(self.filename, 'w') + bfile.write(body_generator.generate_body(length)) + bfile.close() + + def remove_content(self): + """ Remove content file """ + os.unlink(self.filename) + os.rmdir(self.wwwdir) + + def tearDown(self): + super(ResponseTestBase, self).tearDown() + self.remove_content() + + def create_clients(self): + """ Create wrk with specified uri """ + self.clients = [control.Wrk(uri=self.uri)] + + def create_servers_with_body(self, length): + """ Create nginx server with long response body """ + self.create_content(length) + port = tempesta.upstream_port_start_from() + nginx = control.Nginx(listen_port=port) + nginx.config.set_resourse_location(self.wwwdir) + self.servers = [nginx] + +class ResponseTest1k(ResponseTestBase): + """ 1k test """ + def create_servers(self): + self.create_servers_with_body(1024) + + def test(self): + """ Test for 1kbyte body """ + self.generic_test_routine(self.config) + +class ResponseTest1M(ResponseTestBase): + """ 1M test """ + def create_servers(self): + self.create_servers_with_body(1024**2) + + def test(self): + """ Test for 1Mbyte body """ + self.generic_test_routine(self.config) From 024bc2955467408aeef4ca62a1d020edd801ac29 Mon Sep 17 00:00:00 2001 From: Vladislav Tsendrovskii Date: Mon, 29 Jan 2018 20:08:05 +0300 Subject: [PATCH 02/12] Request tests with wrong length expect 403 for wrong length Parsing multiple responses Expect exactly 1 parsing error check for garbage after response end Write special testers for wrong length --- tempesta_fw/t/functional/helpers/chains.py | 17 ++- tempesta_fw/t/functional/helpers/control.py | 24 ++-- tempesta_fw/t/functional/helpers/deproxy.py | 103 ++++++++++++----- tempesta_fw/t/functional/helpers/wrk.py | 39 +++---- .../t/functional/long_body/__init__.py | 2 +- tempesta_fw/t/functional/long_body/client.py | 84 ++++++++++++++ .../functional/long_body/test_long_request.py | 22 +--- .../functional/long_body/test_wrong_length.py | 107 ++++++++++++++++++ 8 files changed, 309 insertions(+), 89 deletions(-) create mode 100644 tempesta_fw/t/functional/long_body/client.py create mode 100644 tempesta_fw/t/functional/long_body/test_wrong_length.py diff --git a/tempesta_fw/t/functional/helpers/chains.py b/tempesta_fw/t/functional/helpers/chains.py index 23174cd28d..a28c5c640a 100644 --- a/tempesta_fw/t/functional/helpers/chains.py +++ b/tempesta_fw/t/functional/helpers/chains.py @@ -85,7 +85,6 @@ def base(uri='/', method='GET', forward=True, date=None): common_resp_date = date # response body common_resp_body = '' - common_resp_body_void = False # common part of response headers common_resp_headers = [ 'Connection: keep-alive', @@ -122,8 +121,6 @@ def base(uri='/', method='GET', forward=True, date=None): ] if method == "GET": common_resp_body = sample_body - else: - common_resp_body_void = True elif method == "POST": common_req_headers += [ @@ -165,12 +162,13 @@ def base(uri='/', method='GET', forward=True, date=None): uri=uri, body=common_req_body ) + tempesta_resp = deproxy.Response.create( status=common_resp_code, headers=common_resp_headers + tempesta_resp_headers_addn, date=common_resp_date, body=common_resp_body, - body_void=common_resp_body_void + method=method, ) if forward: @@ -180,12 +178,13 @@ def base(uri='/', method='GET', forward=True, date=None): uri=uri, body=common_req_body ) + backend_resp = deproxy.Response.create( status=common_resp_code, headers=common_resp_headers + backend_resp_headers_addn, date=common_resp_date, body=common_resp_body, - body_void=common_resp_body_void + method=method, ) else: tempesta_req = None @@ -197,6 +196,14 @@ def base(uri='/', method='GET', forward=True, date=None): server_response=backend_resp) return copy.copy(base_chain) +def response_403(date = None): + if date is None: + date = deproxy.HttpMessage.date_time_string() + resp = deproxy.Response.create(status=403, + headers=['Content-Length: 0'], + date=date, + body='') + return resp def base_chunked(uri='/'): """Same as chains.base(), but returns a copy of message chain with diff --git a/tempesta_fw/t/functional/helpers/control.py b/tempesta_fw/t/functional/helpers/control.py index b12290b49d..1a96004e33 100644 --- a/tempesta_fw/t/functional/helpers/control.py +++ b/tempesta_fw/t/functional/helpers/control.py @@ -127,20 +127,26 @@ def __init__(self, threads=-1, uri='/', ssl=False): Client.__init__(self, binary='wrk', uri=uri, ssl=ssl) self.threads = threads self.script = '' - self.scriptdir = ''.join([os.path.dirname(os.path.realpath(__file__)), '/../wrk/']) + self.local_scriptdir = ''.join([os.path.dirname(os.path.realpath(__file__)), '/../wrk/']) + self.copy_script = True - def set_script(self, script, scriptdir = None): - self.script = script - if scriptdir != None: - self.scriptdir = scriptdir + def set_script(self, script, need_copy=True): + self.script = script + ".lua" + self.copy_script = need_copy def append_script_option(self): if not self.script: return - path = ''.join([self.scriptdir, self.script, '.lua']) - script_path = os.path.abspath(path) - assert os.path.isfile(script_path), \ - 'No script found: %s !' % script_path + script_path = self.workdir + "/" + self.script + + if self.copy_script: + local_path = ''.join([self.local_scriptdir, self.script]) + local_script_path = os.path.abspath(local_path) + assert os.path.isfile(local_script_path), \ + 'No script found: %s !' % local_script_path + f = open(local_script_path, 'r') + self.files.append((self.script, f.read())) + self.options.append('-s %s' % script_path) def form_command(self): diff --git a/tempesta_fw/t/functional/helpers/deproxy.py b/tempesta_fw/t/functional/helpers/deproxy.py index d71b8c3ca5..6011aa72c1 100644 --- a/tempesta_fw/t/functional/helpers/deproxy.py +++ b/tempesta_fw/t/functional/helpers/deproxy.py @@ -207,10 +207,10 @@ def __repr__(self): class HttpMessage(object): __metaclass__ = abc.ABCMeta - def __init__(self, message_text=None, body_parsing=True, body_void=False): + def __init__(self, message_text=None, body_parsing=True, method="GET"): self.msg = '' + self.method = method self.body_parsing = True - self.body_void = body_void # For responses to HEAD requests self.headers = HeaderCollection() self.trailer = HeaderCollection() self.body = '' @@ -222,7 +222,7 @@ def parse_text(self, message_text, body_parsing=True): self.body_parsing = body_parsing stream = StringIO(message_text) self.__parse(stream) - self.__set_str_msg() + self.build_message() def __parse(self, stream): self.parse_firstline(stream) @@ -230,33 +230,36 @@ def __parse(self, stream): self.body = '' self.parse_body(stream) - def __set_str_msg(self): + def build_message(self): self.msg = str(self) @abc.abstractmethod def parse_firstline(self, stream): pass + @abc.abstractmethod + def parse_body(self, stream): + pass + def get_firstline(self): return '' def parse_headers(self, stream): self.headers = HeaderCollection.from_stream(stream) - def parse_body(self, stream): - if self.body_parsing and 'Transfer-Encoding' in self.headers: - enc = self.headers['Transfer-Encoding'] - option = enc.split(',')[-1] # take the last option + def read_encoded_body(self, stream): + """ RFC 7230. 3.3.3 #3 """ + enc = self.headers['Transfer-Encoding'] + option = enc.split(',')[-1] # take the last option - if option.strip().lower() == 'chunked': - self.read_chunked_body(stream) - else: - error.bug('Not implemented!') - elif self.body_parsing and 'Content-Length' in self.headers: - length = int(self.headers['Content-Length']) - self.read_sized_body(stream, length) + if option.strip().lower() == 'chunked': + self.read_chunked_body(stream) else: - self.body = stream.read() + error.bug('Not implemented!') + + def read_rest_body(self, stream): + """ RFC 7230. 3.3.3 #7 """ + self.body = stream.read() def read_chunked_body(self, stream): while True: @@ -278,11 +281,10 @@ def read_chunked_body(self, stream): # Parsing trailer will eat last CRLF self.parse_trailer(stream) - def read_sized_body(self, stream, size): - if self.body_void: - return - if size == 0: - return + def read_sized_body(self, stream): + """ RFC 7230. 3.3.3 #5 """ + size = int(self.headers['Content-Length']) + self.body = stream.read(size) if len(self.body) != size: raise ParseError(("Wrong body size: expect %d but got %d!" @@ -379,6 +381,19 @@ def parse_firstline(self, stream): def get_firstline(self): return ' '.join([self.method, self.uri, self.version]) + def parse_body(self, stream): + """ RFC 7230 3.3.3 """ + # 3.3.3 3 + if 'Transfer-Encoding' in self.headers: + self.read_encoded_body(stream) + return + # 3.3.3 5 + if 'Content-Length' in self.headers: + self.read_sized_body(stream) + return + # 3.3.3 6 + self.body = '' + def __eq__(self, other): return ((self.method == other.method) and (self.version == other.version) @@ -422,6 +437,31 @@ def parse_firstline(self, stream): except: raise ParseError('Invalid Status code!') + def parse_body(self, stream): + """ RFC 7230 3.3.3 """ + # 3.3.3 1 + if self.method == "HEAD": + return + code = int(self.status) + if code >= 100 and code <= 199 or \ + code == 204 or code == 304: + return + # 3.3.3 2 + if self.method == "CONNECT" and code >= 200 and code <= 299: + error.bug('Not implemented!') + return + # 3.3.3 3 + if 'Transfer-Encoding' in self.headers: + self.read_encoded_body(stream) + return + # TODO: check 3.3.3 4 + # 3.3.3 5 + if 'Content-Length' in self.headers: + self.read_sized_body(stream) + return + # 3.3.3 7 + self.read_rest_body(stream) + def get_firstline(self): status = int(self.status) reason = BaseHTTPRequestHandler.responses[status][0] @@ -438,12 +478,12 @@ def __ne__(self, other): @staticmethod def create(status, headers, version='HTTP/1.1', date=False, - srv_version=None, body=None, body_void=False): + srv_version=None, body=None, method='GET'): reason = BaseHTTPRequestHandler.responses first_line = ' '.join([version, str(status), reason[status][0]]) msg = HttpMessage.create(first_line, headers, date=date, srv_version=srv_version, body=body) - return Response(msg, body_void=body_void) + return Response(msg, method=method) #------------------------------------------------------------------------------- # HTTP Client/Server @@ -481,10 +521,10 @@ def run_start(self): def clear(self): self.request_buffer = '' - def set_request(self, request): - if request: - self.request = request - self.request_buffer = request.msg + def set_request(self, request_chain): + if request_chain: + self.request = request_chain.request + self.request_buffer = request_chain.msg def set_tester(self, tester): self.tester = tester @@ -502,8 +542,7 @@ def handle_read(self): tf_cfg.dbg(4, '\tDeproxy: Client: Receive response from Tempesta.') tf_cfg.dbg(5, self.response_buffer) try: - response = Response(self.response_buffer, - body_void=(self.request.method == 'HEAD')) + response = Response(self.response_buffer, method=self.request.method) self.response_buffer = self.response_buffer[len(response.msg):] except IncompliteMessage: return @@ -512,6 +551,9 @@ def handle_read(self): '<<<<<\n%s>>>>>' % self.response_buffer)) raise + if len(self.response_buffer) > 0: + # TODO: take care about pipelined case + raise ParseError('Garbage after response end:\n```\n%s\n```\n' % self.response_buffer) if self.tester: self.tester.recieved_response(response) self.response_buffer = '' @@ -532,6 +574,7 @@ def handle_error(self): error.bug('\tDeproxy: Client: %s' % v) + class ServerConnection(asyncore.dispatcher_with_send): def __init__(self, tester, server, sock=None, keep_alive=None): @@ -732,7 +775,7 @@ def run(self): for self.current_chain in self.message_chains: self.recieved_chain = MessageChain.empty() self.client.clear() - self.client.set_request(self.current_chain.request) + self.client.set_request(self.current_chain) self.loop() self.check_expectations() diff --git a/tempesta_fw/t/functional/helpers/wrk.py b/tempesta_fw/t/functional/helpers/wrk.py index 568c946582..c16452d403 100644 --- a/tempesta_fw/t/functional/helpers/wrk.py +++ b/tempesta_fw/t/functional/helpers/wrk.py @@ -4,7 +4,7 @@ __copyright__ = 'Copyright (C) 2017 Tempesta Technologies, Inc.' __license__ = 'GPL2' -import os +from . import remote class ScriptGenerator(object): """ Generate lua script """ @@ -12,8 +12,9 @@ class ScriptGenerator(object): uri = "/" headers = [] body = "" - filename = None + config = "" def __luaencode(self, value): + # TODO: take care about escaping return value def set_request_type(self, request_type): @@ -30,28 +31,14 @@ def set_body(self, body): def make_config(self, filename): """ Generate config and write it to file """ - self.filename = filename - config = open(filename, 'w') - config.write("local r = {\n") - config.write(" method = \"%s\",\n" % self.__luaencode(self.request_type)) - config.write(" path = \"%s\",\n" % self.__luaencode(self.uri)) - config.write(" headers = {\n") + config = "" + config += "wrk.method = \"%s\"\n" % self.request_type + config +="wrk.path = \"%s\",\n" % self.__luaencode(self.uri) + config += "wrk.headers = {\n" for header in self.headers: - config.write(" [\"%s\"] = \"%s\",\n" % (header[0], header[1])) - config.write(" },\n") - config.write(" body = \"%s\",\n" % self.body) - config.write("}\n") - config.write("local req\n") - config.write("init = function()\n") - config.write(" req = wrk.format(r.method, r.path, r.headers, r.body)\n") - config.write("end\n") - config.write("request = function()\n") - config.write(" return req\n") - config.write("end\n") - config.close() - - def remove_config(self): - """ Remove previously created config """ - if self.filename != None: - os.unlink(self.filename) - self.filename = None + name = self.__luaencode(header[0]) + value = self.__luaencode(header[1]) + config += " [\"%s\"] = \"%s\",\n" % (name, value) + config += "},\n" + config += "wrk.body = \"%s\",\n" % self.__luaencode(self.body) + remote.client.copy_file(filename, config) diff --git a/tempesta_fw/t/functional/long_body/__init__.py b/tempesta_fw/t/functional/long_body/__init__.py index b056d51483..a53c5ed524 100644 --- a/tempesta_fw/t/functional/long_body/__init__.py +++ b/tempesta_fw/t/functional/long_body/__init__.py @@ -1 +1 @@ -__all__ = ['test_long_request', 'testing_long_response', 'body_generator'] +__all__ = ['test_long_request', 'test_long_response', 'body_generator', 'test_wrong_length', 'client'] diff --git a/tempesta_fw/t/functional/long_body/client.py b/tempesta_fw/t/functional/long_body/client.py new file mode 100644 index 0000000000..3ea9b5eb65 --- /dev/null +++ b/tempesta_fw/t/functional/long_body/client.py @@ -0,0 +1,84 @@ +__author__ = 'Tempesta Technologies, Inc.' +__copyright__ = 'Copyright (C) 2017 Tempesta Technologies, Inc.' +__license__ = 'GPL2' + +import asyncore +from helpers import tf_cfg, deproxy + +class ClientMultipleResponses(deproxy.Client): + """ Client with support of parsing multiple responses """ + method = "INVALID" + request_buffer = "" + + def set_request(self, request_chain): + if request_chain != None: + self.method = request_chain.method + self.request_buffer = self.request_buffer + request_chain.request.msg + + def handle_read(self): + self.response_buffer += self.recv(deproxy.MAX_MESSAGE_SIZE) + if not self.response_buffer: + return + tf_cfg.dbg(4, '\tDeproxy: Client: Receive response from Tempesta.') + tf_cfg.dbg(5, self.response_buffer) + + method = self.method + while len(self.response_buffer) > 0: + try: + response = deproxy.Response(self.response_buffer, method=method) + self.response_buffer = self.response_buffer[len(response.msg):] + method = "GET" + except deproxy.ParseError: + tf_cfg.dbg(4, ('Deproxy: Client: Can\'t parse message\n' + '<<<<<\n%s>>>>>' + % self.response_buffer)) + raise + if self.tester: + self.tester.recieved_response(response) + + self.response_buffer = '' + raise asyncore.ExitNow + +class BadLengthMessageChain(deproxy.MessageChain): + def __init__(self, request, expected_responses, forwarded_request=None, + server_response=None): + deproxy.MessageChain.__init__(self, request=request, forwarded_request=forwarded_request, + server_response=server_response, expected_response = None) + self.responses = expected_responses + self.method = request.method + + @staticmethod + def empty(): + return BadLengthMessageChain(deproxy.Request(), []) + +class BadLengthDeproxy(deproxy.Deproxy): + """ Support of invalid length """ + def __compare_messages(self, expected, recieved, message): + expected.set_expected(expected_time_delta=self.timeout) + assert expected == recieved, \ + ("Received message (%s) does not suit expected one!\n\n" + "\tReceieved:\n<<<<<|\n%s|>>>>>\n" + "\tExpected:\n<<<<<|\n%s|>>>>>\n" + % (message, recieved.msg, expected.msg)) + + def run(self): + for self.current_chain in self.message_chains: + self.recieved_chain = BadLengthMessageChain.empty() + self.client.clear() + self.client.set_request(self.current_chain) + self.loop() + self.check_expectations() + + def check_expectations(self): + self.__compare_messages(self.current_chain.fwd_request, self.recieved_chain.fwd_request, 'fwd_request') + nexpected = len(self.current_chain.responses) + nrecieved = len(self.recieved_chain.responses) + assert nexpected == nrecieved, ("Expected %i responses, but recieved %i" % (nexpected, nrecieved)) + for i in range(nexpected): + expected = self.current_chain.responses[i] + recieved = self.recieved_chain.responses[i] + self.__compare_messages(expected, recieved, "response[%i]" % i) + + def recieved_response(self, response): + """Client received response for its request.""" + self.recieved_chain.responses.append(response) diff --git a/tempesta_fw/t/functional/long_body/test_long_request.py b/tempesta_fw/t/functional/long_body/test_long_request.py index e86e27909d..4e89abf39a 100644 --- a/tempesta_fw/t/functional/long_body/test_long_request.py +++ b/tempesta_fw/t/functional/long_body/test_long_request.py @@ -6,7 +6,6 @@ import unittest import body_generator -import os from testers import stress from helpers import tf_cfg, control, tempesta, remote, wrk @@ -14,9 +13,7 @@ class RequestTestBase(stress.StressTest): """ Test long request """ config = "cache 0;\n" - root = "/tmp/long_body/" script = None - scriptdir = root + "scripts/" wrk = None clients = [] generator = None @@ -25,25 +22,14 @@ def create_clients_with_body(self, length): """ Create wrk client with long request body """ self.generator = wrk.ScriptGenerator() self.generator.set_body(body_generator.generate_body(length)) - if not os.path.exists(self.root): - os.mkdir(self.root) - elif not os.path.isdir(self.root): - raise Exception("%s already exists" % self.root) - if not os.path.exists(self.scriptdir): - os.mkdir(self.scriptdir) - elif not os.path.isdir(self.scriptdir): - raise Exception("%s already exists" % self.scriptdir) + filename = self.script + ".lua" + self.generator.make_config(filename) - self.generator.make_config(self.scriptdir + self.script + ".lua") self.wrk = control.Wrk() - self.wrk.set_script(self.script, self.scriptdir) - self.clients = [self.wrk] + self.wrk.set_script(self.script, need_copy=False) - def tearDown(self): - super(RequestTestBase, self).tearDown() - self.generator.remove_config() - os.rmdir(self.scriptdir) + self.clients = [self.wrk] class RequestTest1k(RequestTestBase): """ Test long request """ diff --git a/tempesta_fw/t/functional/long_body/test_wrong_length.py b/tempesta_fw/t/functional/long_body/test_wrong_length.py new file mode 100644 index 0000000000..c14edc2d91 --- /dev/null +++ b/tempesta_fw/t/functional/long_body/test_wrong_length.py @@ -0,0 +1,107 @@ +""" Testing for missing or wrong body length in request/response """ + +__author__ = 'Tempesta Technologies, Inc.' +__copyright__ = 'Copyright (C) 2017 Tempesta Technologies, Inc.' +__license__ = 'GPL2' + +import unittest +import body_generator +import os + +from . import client + +from testers import functional +from helpers import tf_cfg, control, tempesta, remote, deproxy, chains + +def req_body_length(base, length): + """ Generate chain with missing or specified body length """ + for msg in ['request', 'fwd_request']: + field = getattr(base, msg) + field.headers.delete_all('Content-Length') + if length != None: + field.headers.add('Content-Length', '%i' % length) + + base.fwd_request.update() + base.request.build_message() + return base + +def generate_chain(method='GET', expect_403=False): + base = chains.base(method=method) + chain = client.BadLengthMessageChain(request=base.request, + expected_responses=[base.response], + forwarded_request=base.fwd_request, + server_response=base.server_response) + if expect_403: + chain.responses.append(chains.response_403()) + return chain + +class TesterCorrectBodyLength(client.BadLengthDeproxy): + """ Tester """ + def create_base(self): + base = generate_chain(method='PUT') + return (base, len(base.request.body)) + + def __init__(self, *args, **kwargs): + client.BadLengthDeproxy.__init__(self, *args, **kwargs) + base = self.create_base() + self.message_chains = [req_body_length(base[0], base[1])] + self.cookies = [] + +class TesterMissingBodyLength(TesterCorrectBodyLength): + """ Tester """ + def create_base(self): + base = generate_chain(method='PUT', expect_403=True) + return (base, None) + +class TesterSmallBodyLength(TesterCorrectBodyLength): + """ Tester """ + def create_base(self): + base = generate_chain(method='PUT', expect_403=True) + return (base, len(base.request.body) - 15) + +class TesterLargeBodyLength(TesterCorrectBodyLength): + """ Tester """ + def create_base(self): + base = generate_chain(method='PUT', expect_403=True) + return (base, len(base.request.body) + 15) + +class RequestCorrectBodyLength(functional.FunctionalTest): + """ Wrong body length """ + config = 'cache 0;\nblock_action error reply;\nblock_action attack reply;\n' + + def create_client(self): + self.client = client.ClientMultipleResponses() + + def create_tester(self, message_chain): + self.tester = TesterCorrectBodyLength(message_chain, self.client, self.servers) + + def test(self): + """ Test """ + self.generic_test_routine(self.config, []) + +class RequestMissingBodyLength(RequestCorrectBodyLength): + """ Wrong body length """ + + def create_tester(self, message_chain): + self.tester = TesterMissingBodyLength(message_chain, self.client, self.servers) + + def assert_tempesta(self): + msg = 'Tempesta have errors in processing HTTP %s.' + self.assertEqual(self.tempesta.stats.cl_msg_parsing_errors, 1, + msg=(msg % 'requests')) + self.assertEqual(self.tempesta.stats.srv_msg_parsing_errors, 0, + msg=(msg % 'responses')) + self.assertEqual(self.tempesta.stats.cl_msg_other_errors, 0, + msg=(msg % 'requests')) + self.assertEqual(self.tempesta.stats.srv_msg_other_errors, 0, + msg=(msg % 'responses')) + +class RequestSmallBodyLength(RequestMissingBodyLength): + """ Wrong body length """ + def create_tester(self, message_chain): + self.tester = TesterSmallBodyLength(message_chain, self.client, self.servers) + +class RequestLargeBodyLength(RequestMissingBodyLength): + """ Wrong body length """ + def create_tester(self, message_chain): + self.tester = TesterLargeBodyLength(message_chain, self.client, self.servers) From d555dd083c65dc4f3f992ece53a8873beeb1570b Mon Sep 17 00:00:00 2001 From: Vladislav Tsendrovskii Date: Thu, 1 Feb 2018 14:50:41 +0300 Subject: [PATCH 03/12] responses tests Add missing length tests Close connection after response w/out content-length Add tests for multiple or invalid content-length --- tempesta_fw/t/functional/helpers/chains.py | 44 ++- tempesta_fw/t/functional/helpers/deproxy.py | 2 +- .../t/functional/long_body/__init__.py | 3 +- .../long_body/test_request_wrong_length.py | 190 ++++++++++++ .../long_body/test_response_wrong_length.py | 273 ++++++++++++++++++ .../functional/long_body/test_wrong_length.py | 107 ------- .../long_body/{client.py => tester.py} | 2 +- 7 files changed, 509 insertions(+), 112 deletions(-) create mode 100644 tempesta_fw/t/functional/long_body/test_request_wrong_length.py create mode 100644 tempesta_fw/t/functional/long_body/test_response_wrong_length.py delete mode 100644 tempesta_fw/t/functional/long_body/test_wrong_length.py rename tempesta_fw/t/functional/long_body/{client.py => tester.py} (97%) diff --git a/tempesta_fw/t/functional/helpers/chains.py b/tempesta_fw/t/functional/helpers/chains.py index a28c5c640a..4a7e19c4aa 100644 --- a/tempesta_fw/t/functional/helpers/chains.py +++ b/tempesta_fw/t/functional/helpers/chains.py @@ -33,6 +33,27 @@ def make_502_expected(): ) return response +def response_500(): + date = deproxy.HttpMessage.date_time_string() + headers = [ + 'Date: %s' % date, + 'Content-Length: 0', + 'Connection: keep-alive' + ] + response = deproxy.Response.create(status=500, headers=headers) + return response + +def response_502(): + date = deproxy.HttpMessage.date_time_string() + headers = [ + 'Date: %s' % date, + 'Content-Length: 0', + 'Connection: keep-alive' + ] + response = deproxy.Response.create(status=502, headers=headers) + return response + + def base(uri='/', method='GET', forward=True, date=None): """Base message chain. Looks like simple Curl request to Tempesta and response for it. @@ -196,15 +217,34 @@ def base(uri='/', method='GET', forward=True, date=None): server_response=backend_resp) return copy.copy(base_chain) -def response_403(date = None): +def response_403(date=None, connection=None): + if date is None: + date = deproxy.HttpMessage.date_time_string() + if connection is None: + resp = deproxy.Response.create(status=403, + headers=['Content-Length: 0'], + date=date, + body='') + else: + resp = deproxy.Response.create(status=403, + headers=[ + 'Content-Length: 0', + 'Connection: %s' % connection + ], + date=date, + body='') + return resp + +def response_400(date=None): if date is None: date = deproxy.HttpMessage.date_time_string() - resp = deproxy.Response.create(status=403, + resp = deproxy.Response.create(status=400, headers=['Content-Length: 0'], date=date, body='') return resp + def base_chunked(uri='/'): """Same as chains.base(), but returns a copy of message chain with chunked body. diff --git a/tempesta_fw/t/functional/helpers/deproxy.py b/tempesta_fw/t/functional/helpers/deproxy.py index 6011aa72c1..8a16fb1e53 100644 --- a/tempesta_fw/t/functional/helpers/deproxy.py +++ b/tempesta_fw/t/functional/helpers/deproxy.py @@ -524,7 +524,7 @@ def clear(self): def set_request(self, request_chain): if request_chain: self.request = request_chain.request - self.request_buffer = request_chain.msg + self.request_buffer = request_chain.request.msg def set_tester(self, tester): self.tester = tester diff --git a/tempesta_fw/t/functional/long_body/__init__.py b/tempesta_fw/t/functional/long_body/__init__.py index a53c5ed524..2e0da75c1a 100644 --- a/tempesta_fw/t/functional/long_body/__init__.py +++ b/tempesta_fw/t/functional/long_body/__init__.py @@ -1 +1,2 @@ -__all__ = ['test_long_request', 'test_long_response', 'body_generator', 'test_wrong_length', 'client'] +__all__ = ['test_long_request', 'test_long_response', 'body_generator', + 'test_request_wrong_length', 'tester', 'test_response_wrong_length'] diff --git a/tempesta_fw/t/functional/long_body/test_request_wrong_length.py b/tempesta_fw/t/functional/long_body/test_request_wrong_length.py new file mode 100644 index 0000000000..6d270fbbe2 --- /dev/null +++ b/tempesta_fw/t/functional/long_body/test_request_wrong_length.py @@ -0,0 +1,190 @@ +""" Testing for missing or wrong body length in request """ + +__author__ = 'Tempesta Technologies, Inc.' +__copyright__ = 'Copyright (C) 2017 Tempesta Technologies, Inc.' +__license__ = 'GPL2' + +import unittest +import body_generator +import os + +from . import tester + +from testers import functional +from helpers import tf_cfg, control, tempesta, remote, deproxy, chains + +def req_body_length(base, length): + """ Generate chain with missing or specified body length """ + for msg in ['request', 'fwd_request']: + field = getattr(base, msg) + field.headers.delete_all('Content-Length') + if length != None: + field.headers.add('Content-Length', '%i' % length) + + base.fwd_request.update() + base.request.build_message() + return base + +def generate_chain(method='GET', expect_403=False): + base = chains.base(method=method) + chain = tester.BadLengthMessageChain(request=base.request, + expected_responses=[base.response], + forwarded_request=base.fwd_request, + server_response=base.server_response) + if expect_403: + chain.responses.append(chains.response_403()) + return chain + +class TesterCorrectBodyLength(tester.BadLengthDeproxy): + """ Tester """ + def create_base(self): + base = generate_chain(method='PUT') + return (base, len(base.request.body)) + + def __init__(self, *args, **kwargs): + tester.BadLengthDeproxy.__init__(self, *args, **kwargs) + base = self.create_base() + self.message_chains = [req_body_length(base[0], base[1])] + self.cookies = [] + +class TesterMissingBodyLength(TesterCorrectBodyLength): + """ Tester """ + def create_base(self): + base = generate_chain(method='PUT', expect_403=True) + return (base, None) + +class TesterSmallBodyLength(TesterCorrectBodyLength): + """ Tester """ + def create_base(self): + base = generate_chain(method='PUT', expect_403=True) + return (base, len(base.request.body) - 15) + +class TesterDuplicateBodyLength(deproxy.Deproxy): + def __init__(self, *args, **kwargs): + deproxy.Deproxy.__init__(self, *args, **kwargs) + base = chains.base(method='PUT') + cl = base.request.headers['Content-Length'] + + base.request.headers.add('Content-Length', cl) + base.request.build_message() + + base.fwd_request = deproxy.Request() + + base.response = chains.response_403(connection='keep-alive') + + self.message_chains = [base] + self.cookies = [] + +class TesterInvalidBodyLength(deproxy.Deproxy): + def __init__(self, *args, **kwargs): + deproxy.Deproxy.__init__(self, *args, **kwargs) + base = chains.base(method='PUT') + base.request.headers['Content-Length'] = 'invalid' + base.request.build_message() + base.response = chains.response_400() + base.fwd_request = deproxy.Request() + self.message_chains = [base] + self.cookies = [] + +class TesterSecondBodyLength(TesterDuplicateBodyLength): + def second_length(self, content_length): + len = int(content_length) + return "%i" % (len - 1) + + def expected_response(self): + return chains.response_400() + + def __init__(self, *args, **kwargs): + deproxy.Deproxy.__init__(self, *args, **kwargs) + base = chains.base(method='PUT') + cl = base.request.headers['Content-Length'] + + duplicate = self.second_length(cl) + base.request.headers.add('Content-Length', duplicate) + base.request.build_message() + + base.response = self.expected_response() + + base.fwd_request = deproxy.Request() + + self.message_chains = [base] + self.cookies = [] + + +class RequestCorrectBodyLength(functional.FunctionalTest): + """ Wrong body length """ + config = 'cache 0;\nblock_action error reply;\nblock_action attack reply;\n' + + def create_client(self): + self.client = tester.ClientMultipleResponses() + + def create_tester(self, message_chain): + self.tester = TesterCorrectBodyLength(message_chain, self.client, self.servers) + + def test(self): + """ Test """ + self.generic_test_routine(self.config, []) + +class RequestMissingBodyLength(RequestCorrectBodyLength): + """ Wrong body length """ + + def create_tester(self, message_chain): + self.tester = TesterMissingBodyLength(message_chain, self.client, self.servers) + + def assert_tempesta(self): + msg = 'Tempesta have errors in processing HTTP %s.' + self.assertEqual(self.tempesta.stats.cl_msg_parsing_errors, 1, + msg=(msg % 'requests')) + self.assertEqual(self.tempesta.stats.srv_msg_parsing_errors, 0, + msg=(msg % 'responses')) + self.assertEqual(self.tempesta.stats.cl_msg_other_errors, 0, + msg=(msg % 'requests')) + self.assertEqual(self.tempesta.stats.srv_msg_other_errors, 0, + msg=(msg % 'responses')) + +class RequestSmallBodyLength(RequestMissingBodyLength): + """ Wrong body length """ + def create_tester(self, message_chain): + self.tester = TesterSmallBodyLength(message_chain, self.client, self.servers) + +class RequestDuplicateBodyLength(functional.FunctionalTest): + config = 'cache 0;\nblock_action error reply;\nblock_action attack reply;\n' + + def create_client(self): + self.client = deproxy.Client() + + def create_tester(self, message_chain): + self.tester = TesterDuplicateBodyLength(message_chain, self.client, self.servers) + + def test(self): + """ Test """ + self.generic_test_routine(self.config, []) + + def assert_tempesta(self): + msg = 'Tempesta have errors in processing HTTP %s.' + self.assertEqual(self.tempesta.stats.cl_msg_parsing_errors, 1, + msg=(msg % 'requests')) + self.assertEqual(self.tempesta.stats.srv_msg_parsing_errors, 0, + msg=(msg % 'responses')) + self.assertEqual(self.tempesta.stats.cl_msg_other_errors, 0, + msg=(msg % 'requests')) + self.assertEqual(self.tempesta.stats.srv_msg_other_errors, 0, + msg=(msg % 'responses')) + +class RequestSecondBodyLength(RequestDuplicateBodyLength): + def create_tester(self, message_chain): + self.tester = TesterSecondBodyLength(message_chain, self.client, self.servers) + def assert_tempesta(self): + msg = 'Tempesta have errors in processing HTTP %s.' + self.assertEqual(self.tempesta.stats.cl_msg_parsing_errors, 1, + msg=(msg % 'requests')) + self.assertEqual(self.tempesta.stats.srv_msg_parsing_errors, 0, + msg=(msg % 'responses')) + self.assertEqual(self.tempesta.stats.cl_msg_other_errors, 0, + msg=(msg % 'requests')) + self.assertEqual(self.tempesta.stats.srv_msg_other_errors, 0, + msg=(msg % 'responses')) + +class RequestInvalidBodyLength(RequestSecondBodyLength): + def create_tester(self, message_chain): + self.tester = TesterInvalidBodyLength(message_chain, self.client, self.servers) diff --git a/tempesta_fw/t/functional/long_body/test_response_wrong_length.py b/tempesta_fw/t/functional/long_body/test_response_wrong_length.py new file mode 100644 index 0000000000..31fcdee910 --- /dev/null +++ b/tempesta_fw/t/functional/long_body/test_response_wrong_length.py @@ -0,0 +1,273 @@ +""" Testing for missing or wrong body length in response """ + +__author__ = 'Tempesta Technologies, Inc.' +__copyright__ = 'Copyright (C) 2017 Tempesta Technologies, Inc.' +__license__ = 'GPL2' + +import unittest +import body_generator +import os + +from . import tester + +from testers import functional +from helpers import tf_cfg, control, tempesta, remote, deproxy, chains + +def resp_body_length(base, length): + """ Generate chain with missing or specified body length """ + for msg in ['response', 'server_response']: + field = getattr(base, msg) + field.headers.delete_all('Content-Length') + if length != None: + actual_len = len(field.body) + if msg == 'response' and actual_len < length: + field.headers.add('Content-Length', '%i' % actual_len) + else: + field.headers.add('Content-Length', '%i' % length) + + base.response.update() + base.server_response.build_message() + return base + +def generate_chain_200(method='GET', response_body=""): + base = chains.base(method=method) + base.response.status = "200" + base.response.body = response_body + base.response.headers['Content-Length'] = len(response_body) + base.server_response.status = "200" + base.server_response.body = response_body + base.server_response.headers['Content-Length'] = len(response_body) + base.response.update() + base.server_response.update() + return base + +def generate_chain_204(method='GET'): + base = chains.base(method=method) + base.response.status = "204" # it's default, but for explicity + base.response.body = "" + base.server_response.status = "204" + base.server_response.body = "" + return base + +class TesterCorrectEmptyBodyLength(deproxy.Deproxy): + """ Tester """ + def create_base(self): + base = generate_chain_200(method='GET') + return (base, len(base.response.body)) + + def __init__(self, *args, **kwargs): + deproxy.Deproxy.__init__(self, *args, **kwargs) + base = self.create_base() + self.message_chains = [resp_body_length(base[0], base[1])] + self.cookies = [] + +class TesterCorrectBodyLength(deproxy.Deproxy): + """ Tester """ + def create_base(self): + base = generate_chain_200(method='GET', response_body="abcd") + return (base, len(base.response.body)) + + def __init__(self, *args, **kwargs): + deproxy.Deproxy.__init__(self, *args, **kwargs) + base = self.create_base() + self.message_chains = [resp_body_length(base[0], base[1])] + self.cookies = [] + +class TesterMissingEmptyBodyLength(deproxy.Deproxy): + """ Tester """ + reply_body = "" + + def __init__(self, *args, **kwargs): + deproxy.Deproxy.__init__(self, *args, **kwargs) + chain = generate_chain_200(method='GET', response_body=self.reply_body) + chain.server_response.headers.delete_all('Content-Length') + chain.server_response.update() + self.message_chains = [chain] + self.cookies = [] + +class TesterMissingBodyLength(TesterMissingEmptyBodyLength): + """ Tester """ + reply_body = "abcdefgh" + +class TesterSmallBodyLength(TesterCorrectBodyLength): + """ Tester """ + def create_base(self): + base = generate_chain_200(method='GET', response_body="abcdefgh") + return (base, len(base.response.body) - 1) + +class TesterForbiddenZeroBodyLength(deproxy.Deproxy): + """ Tester """ + def __init__(self, *args, **kwargs): + deproxy.Deproxy.__init__(self, *args, **kwargs) + base = self.create_base() + base[0].server_response.headers.delete_all('Content-Length') + base[0].server_response.headers.add('Content-Length', "%i" % base[1]) + base[0].server_response.build_message() + + base[0].response = chains.response_500() + + self.message_chains = [base[0]] + self.cookies = [] + + def create_base(self): + base = generate_chain_204(method='GET') + return (base, 0) + +class TesterForbiddenPositiveBodyLength(TesterForbiddenZeroBodyLength): + """ Tester """ + def create_base(self): + base = generate_chain_204(method='GET') + return (base, 1) + +class TesterDuplicateBodyLength(deproxy.Deproxy): + """ Tester """ + def __init__(self, *args, **kwargs): + deproxy.Deproxy.__init__(self, *args, **kwargs) + base = self.create_base() + cl = base[0].server_response.headers['Content-Length'] + base[0].server_response.headers.add('Content-Length', cl) + base[0].server_response.build_message() + + base[0].response = chains.response_500() + + self.message_chains = [base[0]] + self.cookies = [] + + def create_base(self): + base = generate_chain_204(method='GET') + return (base, 0) + +class TesterSecondBodyLength(deproxy.Deproxy): + """ Tester """ + def __init__(self, *args, **kwargs): + deproxy.Deproxy.__init__(self, *args, **kwargs) + base = self.create_base() + cl = base[0].server_response.headers['Content-Length'] + length = int(cl) + base[0].server_response.headers.add('Content-Length', "%i" % (length - 1)) + base[0].server_response.build_message() + + base[0].response = chains.response_502() + + self.message_chains = [base[0]] + self.cookies = [] + + def create_base(self): + base = generate_chain_204(method='GET') + return (base, 0) + +class TesterInvalidBodyLength(deproxy.Deproxy): + """ Tester """ + def __init__(self, *args, **kwargs): + deproxy.Deproxy.__init__(self, *args, **kwargs) + base = self.create_base() + base[0].server_response.headers['Content-Length'] = "invalid" + base[0].server_response.build_message() + + base[0].response = chains.response_502() + + self.message_chains = [base[0]] + self.cookies = [] + + def create_base(self): + base = generate_chain_204(method='GET') + return (base, 0) + +class ResponseCorrectEmptyBodyLength(functional.FunctionalTest): + """ Correct body length """ + config = 'cache 0;\nblock_action error reply;\nblock_action attack reply;\n' + + def create_client(self): + self.client = deproxy.Client() + + def create_tester(self, message_chain): + self.tester = TesterCorrectEmptyBodyLength(message_chain, self.client, self.servers) + + def test(self): + """ Test """ + self.generic_test_routine(self.config, []) + +class ResponseCorrectBodyLength(ResponseCorrectEmptyBodyLength): + """ Correct body length """ + + def create_tester(self, message_chain): + self.tester = TesterCorrectBodyLength(message_chain, self.client, self.servers) + +class ResponseMissingEmptyBodyLength(ResponseCorrectEmptyBodyLength): + """ Missing body length """ + + def create_servers(self): + port = tempesta.upstream_port_start_from() + self.servers = [deproxy.Server(port=port, keep_alive=1)] + + def create_tester(self, message_chain): + self.tester = TesterMissingEmptyBodyLength(message_chain, self.client, self.servers) + +class ResponseMissingBodyLength(ResponseMissingEmptyBodyLength): + """ Missing body length """ + + def create_tester(self, message_chain): + self.tester = TesterMissingBodyLength(message_chain, self.client, self.servers) + +class ResponseSmallBodyLength(ResponseCorrectEmptyBodyLength): + """ Small body length """ + + def create_tester(self, message_chain): + self.tester = TesterSmallBodyLength(message_chain, self.client, self.servers) + + def assert_tempesta(self): + msg = 'Tempesta have errors in processing HTTP %s.' + self.assertEqual(self.tempesta.stats.cl_msg_parsing_errors, 0, + msg=(msg % 'requests')) + self.assertEqual(self.tempesta.stats.srv_msg_parsing_errors, 1, + msg=(msg % 'responses')) + self.assertEqual(self.tempesta.stats.cl_msg_other_errors, 0, + msg=(msg % 'requests')) + self.assertEqual(self.tempesta.stats.srv_msg_other_errors, 1, + msg=(msg % 'responses')) + +class ResponseForbiddenZeroBodyLength(ResponseCorrectEmptyBodyLength): + """ Forbidden body length """ + + def create_tester(self, message_chain): + self.tester = TesterForbiddenZeroBodyLength(message_chain, self.client, self.servers) + + def assert_tempesta(self): + msg = 'Tempesta have errors in processing HTTP %s.' + self.assertEqual(self.tempesta.stats.cl_msg_parsing_errors, 0, + msg=(msg % 'requests')) + self.assertEqual(self.tempesta.stats.srv_msg_parsing_errors, 1, + msg=(msg % 'responses')) + self.assertEqual(self.tempesta.stats.cl_msg_other_errors, 0, + msg=(msg % 'requests')) + self.assertEqual(self.tempesta.stats.srv_msg_other_errors, 0, + msg=(msg % 'responses')) + + +class ResponseForbiddenPositiveBodyLength(ResponseForbiddenZeroBodyLength): + """ Forbidden body length """ + + def create_tester(self, message_chain): + self.tester = TesterForbiddenPositiveBodyLength(message_chain, self.client, self.servers) + +class ResponseDuplicateBodyLength(ResponseCorrectEmptyBodyLength): + def create_tester(self, message_chain): + self.tester = TesterDuplicateBodyLength(message_chain, self.client, self.servers) + def assert_tempesta(self): + msg = 'Tempesta have errors in processing HTTP %s.' + self.assertEqual(self.tempesta.stats.cl_msg_parsing_errors, 0, + msg=(msg % 'requests')) + self.assertEqual(self.tempesta.stats.srv_msg_parsing_errors, 1, + msg=(msg % 'responses')) + self.assertEqual(self.tempesta.stats.cl_msg_other_errors, 0, + msg=(msg % 'requests')) + self.assertEqual(self.tempesta.stats.srv_msg_other_errors, 0, + msg=(msg % 'responses')) + +class ResponseSecondBodyLength(ResponseDuplicateBodyLength): + def create_tester(self, message_chain): + self.tester = TesterSecondBodyLength(message_chain, self.client, self.servers) + +class ResponseInvalidBodyLength(ResponseDuplicateBodyLength): + def create_tester(self, message_chain): + self.tester = TesterInvalidBodyLength(message_chain, self.client, self.servers) diff --git a/tempesta_fw/t/functional/long_body/test_wrong_length.py b/tempesta_fw/t/functional/long_body/test_wrong_length.py deleted file mode 100644 index c14edc2d91..0000000000 --- a/tempesta_fw/t/functional/long_body/test_wrong_length.py +++ /dev/null @@ -1,107 +0,0 @@ -""" Testing for missing or wrong body length in request/response """ - -__author__ = 'Tempesta Technologies, Inc.' -__copyright__ = 'Copyright (C) 2017 Tempesta Technologies, Inc.' -__license__ = 'GPL2' - -import unittest -import body_generator -import os - -from . import client - -from testers import functional -from helpers import tf_cfg, control, tempesta, remote, deproxy, chains - -def req_body_length(base, length): - """ Generate chain with missing or specified body length """ - for msg in ['request', 'fwd_request']: - field = getattr(base, msg) - field.headers.delete_all('Content-Length') - if length != None: - field.headers.add('Content-Length', '%i' % length) - - base.fwd_request.update() - base.request.build_message() - return base - -def generate_chain(method='GET', expect_403=False): - base = chains.base(method=method) - chain = client.BadLengthMessageChain(request=base.request, - expected_responses=[base.response], - forwarded_request=base.fwd_request, - server_response=base.server_response) - if expect_403: - chain.responses.append(chains.response_403()) - return chain - -class TesterCorrectBodyLength(client.BadLengthDeproxy): - """ Tester """ - def create_base(self): - base = generate_chain(method='PUT') - return (base, len(base.request.body)) - - def __init__(self, *args, **kwargs): - client.BadLengthDeproxy.__init__(self, *args, **kwargs) - base = self.create_base() - self.message_chains = [req_body_length(base[0], base[1])] - self.cookies = [] - -class TesterMissingBodyLength(TesterCorrectBodyLength): - """ Tester """ - def create_base(self): - base = generate_chain(method='PUT', expect_403=True) - return (base, None) - -class TesterSmallBodyLength(TesterCorrectBodyLength): - """ Tester """ - def create_base(self): - base = generate_chain(method='PUT', expect_403=True) - return (base, len(base.request.body) - 15) - -class TesterLargeBodyLength(TesterCorrectBodyLength): - """ Tester """ - def create_base(self): - base = generate_chain(method='PUT', expect_403=True) - return (base, len(base.request.body) + 15) - -class RequestCorrectBodyLength(functional.FunctionalTest): - """ Wrong body length """ - config = 'cache 0;\nblock_action error reply;\nblock_action attack reply;\n' - - def create_client(self): - self.client = client.ClientMultipleResponses() - - def create_tester(self, message_chain): - self.tester = TesterCorrectBodyLength(message_chain, self.client, self.servers) - - def test(self): - """ Test """ - self.generic_test_routine(self.config, []) - -class RequestMissingBodyLength(RequestCorrectBodyLength): - """ Wrong body length """ - - def create_tester(self, message_chain): - self.tester = TesterMissingBodyLength(message_chain, self.client, self.servers) - - def assert_tempesta(self): - msg = 'Tempesta have errors in processing HTTP %s.' - self.assertEqual(self.tempesta.stats.cl_msg_parsing_errors, 1, - msg=(msg % 'requests')) - self.assertEqual(self.tempesta.stats.srv_msg_parsing_errors, 0, - msg=(msg % 'responses')) - self.assertEqual(self.tempesta.stats.cl_msg_other_errors, 0, - msg=(msg % 'requests')) - self.assertEqual(self.tempesta.stats.srv_msg_other_errors, 0, - msg=(msg % 'responses')) - -class RequestSmallBodyLength(RequestMissingBodyLength): - """ Wrong body length """ - def create_tester(self, message_chain): - self.tester = TesterSmallBodyLength(message_chain, self.client, self.servers) - -class RequestLargeBodyLength(RequestMissingBodyLength): - """ Wrong body length """ - def create_tester(self, message_chain): - self.tester = TesterLargeBodyLength(message_chain, self.client, self.servers) diff --git a/tempesta_fw/t/functional/long_body/client.py b/tempesta_fw/t/functional/long_body/tester.py similarity index 97% rename from tempesta_fw/t/functional/long_body/client.py rename to tempesta_fw/t/functional/long_body/tester.py index 3ea9b5eb65..58951cd310 100644 --- a/tempesta_fw/t/functional/long_body/client.py +++ b/tempesta_fw/t/functional/long_body/tester.py @@ -13,7 +13,7 @@ class ClientMultipleResponses(deproxy.Client): def set_request(self, request_chain): if request_chain != None: self.method = request_chain.method - self.request_buffer = self.request_buffer + request_chain.request.msg + self.request_buffer = request_chain.request.msg def handle_read(self): self.response_buffer += self.recv(deproxy.MAX_MESSAGE_SIZE) From 2527f60040b4895f583e39151e7b33121f4c3c98 Mon Sep 17 00:00:00 2001 From: Vladislav Tsendrovskii Date: Sun, 18 Feb 2018 00:54:38 +0300 Subject: [PATCH 04/12] Fixes --- tempesta_fw/t/functional/helpers/chains.py | 3 +- tempesta_fw/t/functional/helpers/control.py | 4 +- tempesta_fw/t/functional/helpers/deproxy.py | 14 +++--- tempesta_fw/t/functional/helpers/wrk.py | 5 +- .../t/functional/long_body/body_generator.py | 2 +- .../functional/long_body/test_long_request.py | 2 +- .../long_body/test_long_response.py | 33 +++++-------- .../long_body/test_request_wrong_length.py | 27 ++++++----- .../long_body/test_response_wrong_length.py | 47 ++++++++++--------- tempesta_fw/t/functional/long_body/tester.py | 14 ++++-- tempesta_fw/t/functional/sched/test_http.py | 2 +- 11 files changed, 79 insertions(+), 74 deletions(-) diff --git a/tempesta_fw/t/functional/helpers/chains.py b/tempesta_fw/t/functional/helpers/chains.py index 4a7e19c4aa..3fcceae59a 100644 --- a/tempesta_fw/t/functional/helpers/chains.py +++ b/tempesta_fw/t/functional/helpers/chains.py @@ -239,7 +239,8 @@ def response_400(date=None): if date is None: date = deproxy.HttpMessage.date_time_string() resp = deproxy.Response.create(status=400, - headers=['Content-Length: 0'], + headers=['Content-Length: 0', + 'Connection: keep-alive'], date=date, body='') return resp diff --git a/tempesta_fw/t/functional/helpers/control.py b/tempesta_fw/t/functional/helpers/control.py index 1a96004e33..9eb09c41af 100644 --- a/tempesta_fw/t/functional/helpers/control.py +++ b/tempesta_fw/t/functional/helpers/control.py @@ -127,7 +127,9 @@ def __init__(self, threads=-1, uri='/', ssl=False): Client.__init__(self, binary='wrk', uri=uri, ssl=ssl) self.threads = threads self.script = '' - self.local_scriptdir = ''.join([os.path.dirname(os.path.realpath(__file__)), '/../wrk/']) + self.local_scriptdir = ''.join([ + os.path.dirname(os.path.realpath(__file__)), + '/../wrk/']) self.copy_script = True def set_script(self, script, need_copy=True): diff --git a/tempesta_fw/t/functional/helpers/deproxy.py b/tempesta_fw/t/functional/helpers/deproxy.py index 8a16fb1e53..d3f8b2db88 100644 --- a/tempesta_fw/t/functional/helpers/deproxy.py +++ b/tempesta_fw/t/functional/helpers/deproxy.py @@ -521,10 +521,10 @@ def run_start(self): def clear(self): self.request_buffer = '' - def set_request(self, request_chain): - if request_chain: - self.request = request_chain.request - self.request_buffer = request_chain.request.msg + def set_request(self, message_chain): + if message_chain: + self.request = message_chain.request + self.request_buffer = message_chain.request.msg def set_tester(self, tester): self.tester = tester @@ -542,7 +542,8 @@ def handle_read(self): tf_cfg.dbg(4, '\tDeproxy: Client: Receive response from Tempesta.') tf_cfg.dbg(5, self.response_buffer) try: - response = Response(self.response_buffer, method=self.request.method) + response = Response(self.response_buffer, + method=self.request.method) self.response_buffer = self.response_buffer[len(response.msg):] except IncompliteMessage: return @@ -553,7 +554,8 @@ def handle_read(self): raise if len(self.response_buffer) > 0: # TODO: take care about pipelined case - raise ParseError('Garbage after response end:\n```\n%s\n```\n' % self.response_buffer) + raise ParseError('Garbage after response end:\n```\n%s\n```\n' % \ + self.response_buffer) if self.tester: self.tester.recieved_response(response) self.response_buffer = '' diff --git a/tempesta_fw/t/functional/helpers/wrk.py b/tempesta_fw/t/functional/helpers/wrk.py index c16452d403..d7b0610f8a 100644 --- a/tempesta_fw/t/functional/helpers/wrk.py +++ b/tempesta_fw/t/functional/helpers/wrk.py @@ -1,7 +1,7 @@ """ Wrk script generator """ __author__ = 'Tempesta Technologies, Inc.' -__copyright__ = 'Copyright (C) 2017 Tempesta Technologies, Inc.' +__copyright__ = 'Copyright (C) 2017-2018 Tempesta Technologies, Inc.' __license__ = 'GPL2' from . import remote @@ -15,6 +15,7 @@ class ScriptGenerator(object): config = "" def __luaencode(self, value): # TODO: take care about escaping + # if we have tests with special symbols in content return value def set_request_type(self, request_type): @@ -33,7 +34,7 @@ def make_config(self, filename): """ Generate config and write it to file """ config = "" config += "wrk.method = \"%s\"\n" % self.request_type - config +="wrk.path = \"%s\",\n" % self.__luaencode(self.uri) + config +="wrk.path = \"%s\"\n" % self.__luaencode(self.uri) config += "wrk.headers = {\n" for header in self.headers: name = self.__luaencode(header[0]) diff --git a/tempesta_fw/t/functional/long_body/body_generator.py b/tempesta_fw/t/functional/long_body/body_generator.py index c92d61cb56..0dc7e96d1a 100644 --- a/tempesta_fw/t/functional/long_body/body_generator.py +++ b/tempesta_fw/t/functional/long_body/body_generator.py @@ -1,5 +1,5 @@ __author__ = 'Tempesta Technologies, Inc.' -__copyright__ = 'Copyright (C) 2017 Tempesta Technologies, Inc.' +__copyright__ = 'Copyright (C) 2017-2018 Tempesta Technologies, Inc.' __license__ = 'GPL2' def generate_body(length): diff --git a/tempesta_fw/t/functional/long_body/test_long_request.py b/tempesta_fw/t/functional/long_body/test_long_request.py index 4e89abf39a..85db72dd6d 100644 --- a/tempesta_fw/t/functional/long_body/test_long_request.py +++ b/tempesta_fw/t/functional/long_body/test_long_request.py @@ -1,7 +1,7 @@ """ Testing for long body in request """ __author__ = 'Tempesta Technologies, Inc.' -__copyright__ = 'Copyright (C) 2017 Tempesta Technologies, Inc.' +__copyright__ = 'Copyright (C) 2017-2018 Tempesta Technologies, Inc.' __license__ = 'GPL2' import unittest diff --git a/tempesta_fw/t/functional/long_body/test_long_response.py b/tempesta_fw/t/functional/long_body/test_long_response.py index fb56ed51fd..a77f16462a 100644 --- a/tempesta_fw/t/functional/long_body/test_long_response.py +++ b/tempesta_fw/t/functional/long_body/test_long_response.py @@ -1,7 +1,7 @@ """ Testing for long body in response """ __author__ = 'Tempesta Technologies, Inc.' -__copyright__ = 'Copyright (C) 2017 Tempesta Technologies, Inc.' +__copyright__ = 'Copyright (C) 2017-2018 Tempesta Technologies, Inc.' __license__ = 'GPL2' import os @@ -13,31 +13,23 @@ class ResponseTestBase(stress.StressTest): """ Test long response """ config = "cache 0;\n" - root = "/tmp/long_body/" - wwwdir = root + "www/" - uri = "/long.bin" - filename = wwwdir + uri + filename = "long.bin" + uri = "/" + filename + fullname = "" def create_content(self, length): """ Create content file """ - if not os.path.exists(self.root): - os.mkdir(self.root) - elif not os.path.isdir(self.root): - raise Exception("%s already exists" % self.root) - - if not os.path.exists(self.wwwdir): - os.mkdir(self.wwwdir) - elif not os.path.isdir(self.wwwdir): - raise Exception("%s already exists" % self.wwwdir) - - bfile = open(self.filename, 'w') - bfile.write(body_generator.generate_body(length)) - bfile.close() + content = body_generator.generate_body(length) + location = tf_cfg.cfg.get('Server', 'resources') + self.fullname = os.path.join(location, self.filename) + tf_cfg.dbg(3, "Copy %s to %s" % (self.filename, self.fullname)) + remote.server.copy_file(self.fullname, content) def remove_content(self): """ Remove content file """ - os.unlink(self.filename) - os.rmdir(self.wwwdir) + if not remote.DEBUG_FILES: + tf_cfg.dbg(3, "Remove %s" % self.fullname) + remote.server.run_cmd("rm %s" % self.fullname) def tearDown(self): super(ResponseTestBase, self).tearDown() @@ -52,7 +44,6 @@ def create_servers_with_body(self, length): self.create_content(length) port = tempesta.upstream_port_start_from() nginx = control.Nginx(listen_port=port) - nginx.config.set_resourse_location(self.wwwdir) self.servers = [nginx] class ResponseTest1k(ResponseTestBase): diff --git a/tempesta_fw/t/functional/long_body/test_request_wrong_length.py b/tempesta_fw/t/functional/long_body/test_request_wrong_length.py index 6d270fbbe2..a21735c40c 100644 --- a/tempesta_fw/t/functional/long_body/test_request_wrong_length.py +++ b/tempesta_fw/t/functional/long_body/test_request_wrong_length.py @@ -1,7 +1,7 @@ """ Testing for missing or wrong body length in request """ __author__ = 'Tempesta Technologies, Inc.' -__copyright__ = 'Copyright (C) 2017 Tempesta Technologies, Inc.' +__copyright__ = 'Copyright (C) 2017-2018 Tempesta Technologies, Inc.' __license__ = 'GPL2' import unittest @@ -118,8 +118,8 @@ class RequestCorrectBodyLength(functional.FunctionalTest): def create_client(self): self.client = tester.ClientMultipleResponses() - def create_tester(self, message_chain): - self.tester = TesterCorrectBodyLength(message_chain, self.client, self.servers) + def create_tester(self): + self.tester = TesterCorrectBodyLength(self.client, self.servers) def test(self): """ Test """ @@ -128,8 +128,8 @@ def test(self): class RequestMissingBodyLength(RequestCorrectBodyLength): """ Wrong body length """ - def create_tester(self, message_chain): - self.tester = TesterMissingBodyLength(message_chain, self.client, self.servers) + def create_tester(self): + self.tester = TesterMissingBodyLength(self.client, self.servers) def assert_tempesta(self): msg = 'Tempesta have errors in processing HTTP %s.' @@ -144,8 +144,8 @@ def assert_tempesta(self): class RequestSmallBodyLength(RequestMissingBodyLength): """ Wrong body length """ - def create_tester(self, message_chain): - self.tester = TesterSmallBodyLength(message_chain, self.client, self.servers) + def create_tester(self): + self.tester = TesterSmallBodyLength(self.client, self.servers) class RequestDuplicateBodyLength(functional.FunctionalTest): config = 'cache 0;\nblock_action error reply;\nblock_action attack reply;\n' @@ -153,8 +153,8 @@ class RequestDuplicateBodyLength(functional.FunctionalTest): def create_client(self): self.client = deproxy.Client() - def create_tester(self, message_chain): - self.tester = TesterDuplicateBodyLength(message_chain, self.client, self.servers) + def create_tester(self): + self.tester = TesterDuplicateBodyLength(self.client, self.servers) def test(self): """ Test """ @@ -172,8 +172,9 @@ def assert_tempesta(self): msg=(msg % 'responses')) class RequestSecondBodyLength(RequestDuplicateBodyLength): - def create_tester(self, message_chain): - self.tester = TesterSecondBodyLength(message_chain, self.client, self.servers) + def create_tester(self): + self.tester = TesterSecondBodyLength(self.client, self.servers) + def assert_tempesta(self): msg = 'Tempesta have errors in processing HTTP %s.' self.assertEqual(self.tempesta.stats.cl_msg_parsing_errors, 1, @@ -186,5 +187,5 @@ def assert_tempesta(self): msg=(msg % 'responses')) class RequestInvalidBodyLength(RequestSecondBodyLength): - def create_tester(self, message_chain): - self.tester = TesterInvalidBodyLength(message_chain, self.client, self.servers) + def create_tester(self): + self.tester = TesterInvalidBodyLength(self.client, self.servers) diff --git a/tempesta_fw/t/functional/long_body/test_response_wrong_length.py b/tempesta_fw/t/functional/long_body/test_response_wrong_length.py index 31fcdee910..33c8812c96 100644 --- a/tempesta_fw/t/functional/long_body/test_response_wrong_length.py +++ b/tempesta_fw/t/functional/long_body/test_response_wrong_length.py @@ -1,7 +1,7 @@ """ Testing for missing or wrong body length in response """ __author__ = 'Tempesta Technologies, Inc.' -__copyright__ = 'Copyright (C) 2017 Tempesta Technologies, Inc.' +__copyright__ = 'Copyright (C) 2017-2018 Tempesta Technologies, Inc.' __license__ = 'GPL2' import unittest @@ -144,7 +144,8 @@ def __init__(self, *args, **kwargs): base = self.create_base() cl = base[0].server_response.headers['Content-Length'] length = int(cl) - base[0].server_response.headers.add('Content-Length', "%i" % (length - 1)) + base[0].server_response.headers.add('Content-Length', + "%i" % (length - 1)) base[0].server_response.build_message() base[0].response = chains.response_502() @@ -180,8 +181,8 @@ class ResponseCorrectEmptyBodyLength(functional.FunctionalTest): def create_client(self): self.client = deproxy.Client() - def create_tester(self, message_chain): - self.tester = TesterCorrectEmptyBodyLength(message_chain, self.client, self.servers) + def create_tester(self): + self.tester = TesterCorrectEmptyBodyLength(self.client, self.servers) def test(self): """ Test """ @@ -190,8 +191,8 @@ def test(self): class ResponseCorrectBodyLength(ResponseCorrectEmptyBodyLength): """ Correct body length """ - def create_tester(self, message_chain): - self.tester = TesterCorrectBodyLength(message_chain, self.client, self.servers) + def create_tester(self): + self.tester = TesterCorrectBodyLength(self.client, self.servers) class ResponseMissingEmptyBodyLength(ResponseCorrectEmptyBodyLength): """ Missing body length """ @@ -200,20 +201,20 @@ def create_servers(self): port = tempesta.upstream_port_start_from() self.servers = [deproxy.Server(port=port, keep_alive=1)] - def create_tester(self, message_chain): - self.tester = TesterMissingEmptyBodyLength(message_chain, self.client, self.servers) + def create_tester(self): + self.tester = TesterMissingEmptyBodyLength(self.client, self.servers) class ResponseMissingBodyLength(ResponseMissingEmptyBodyLength): """ Missing body length """ - def create_tester(self, message_chain): - self.tester = TesterMissingBodyLength(message_chain, self.client, self.servers) + def create_tester(self): + self.tester = TesterMissingBodyLength(self.client, self.servers) class ResponseSmallBodyLength(ResponseCorrectEmptyBodyLength): """ Small body length """ - def create_tester(self, message_chain): - self.tester = TesterSmallBodyLength(message_chain, self.client, self.servers) + def create_tester(self): + self.tester = TesterSmallBodyLength(self.client, self.servers) def assert_tempesta(self): msg = 'Tempesta have errors in processing HTTP %s.' @@ -229,8 +230,8 @@ def assert_tempesta(self): class ResponseForbiddenZeroBodyLength(ResponseCorrectEmptyBodyLength): """ Forbidden body length """ - def create_tester(self, message_chain): - self.tester = TesterForbiddenZeroBodyLength(message_chain, self.client, self.servers) + def create_tester(self): + self.tester = TesterForbiddenZeroBodyLength(self.client, self.servers) def assert_tempesta(self): msg = 'Tempesta have errors in processing HTTP %s.' @@ -247,12 +248,14 @@ def assert_tempesta(self): class ResponseForbiddenPositiveBodyLength(ResponseForbiddenZeroBodyLength): """ Forbidden body length """ - def create_tester(self, message_chain): - self.tester = TesterForbiddenPositiveBodyLength(message_chain, self.client, self.servers) + def create_tester(self): + self.tester = TesterForbiddenPositiveBodyLength(self.client, + self.servers) class ResponseDuplicateBodyLength(ResponseCorrectEmptyBodyLength): - def create_tester(self, message_chain): - self.tester = TesterDuplicateBodyLength(message_chain, self.client, self.servers) + def create_tester(self): + self.tester = TesterDuplicateBodyLength(self.client, self.servers) + def assert_tempesta(self): msg = 'Tempesta have errors in processing HTTP %s.' self.assertEqual(self.tempesta.stats.cl_msg_parsing_errors, 0, @@ -265,9 +268,9 @@ def assert_tempesta(self): msg=(msg % 'responses')) class ResponseSecondBodyLength(ResponseDuplicateBodyLength): - def create_tester(self, message_chain): - self.tester = TesterSecondBodyLength(message_chain, self.client, self.servers) + def create_tester(self): + self.tester = TesterSecondBodyLength(self.client, self.servers) class ResponseInvalidBodyLength(ResponseDuplicateBodyLength): - def create_tester(self, message_chain): - self.tester = TesterInvalidBodyLength(message_chain, self.client, self.servers) + def create_tester(self): + self.tester = TesterInvalidBodyLength(self.client, self.servers) diff --git a/tempesta_fw/t/functional/long_body/tester.py b/tempesta_fw/t/functional/long_body/tester.py index 58951cd310..fb74138b48 100644 --- a/tempesta_fw/t/functional/long_body/tester.py +++ b/tempesta_fw/t/functional/long_body/tester.py @@ -1,5 +1,5 @@ __author__ = 'Tempesta Technologies, Inc.' -__copyright__ = 'Copyright (C) 2017 Tempesta Technologies, Inc.' +__copyright__ = 'Copyright (C) 2017-2018 Tempesta Technologies, Inc.' __license__ = 'GPL2' import asyncore @@ -42,8 +42,10 @@ def handle_read(self): class BadLengthMessageChain(deproxy.MessageChain): def __init__(self, request, expected_responses, forwarded_request=None, server_response=None): - deproxy.MessageChain.__init__(self, request=request, forwarded_request=forwarded_request, - server_response=server_response, expected_response = None) + deproxy.MessageChain.__init__(self, request=request, + forwarded_request=forwarded_request, + server_response=server_response, + expected_response = None) self.responses = expected_responses self.method = request.method @@ -70,10 +72,12 @@ def run(self): self.check_expectations() def check_expectations(self): - self.__compare_messages(self.current_chain.fwd_request, self.recieved_chain.fwd_request, 'fwd_request') + self.__compare_messages(self.current_chain.fwd_request, + self.recieved_chain.fwd_request, 'fwd_request') nexpected = len(self.current_chain.responses) nrecieved = len(self.recieved_chain.responses) - assert nexpected == nrecieved, ("Expected %i responses, but recieved %i" % (nexpected, nrecieved)) + assert nexpected == nrecieved, \ + ("Expected %i responses, but recieved %i" % (nexpected, nrecieved)) for i in range(nexpected): expected = self.current_chain.responses[i] recieved = self.recieved_chain.responses[i] diff --git a/tempesta_fw/t/functional/sched/test_http.py b/tempesta_fw/t/functional/sched/test_http.py index d377bf0589..58949aae1a 100644 --- a/tempesta_fw/t/functional/sched/test_http.py +++ b/tempesta_fw/t/functional/sched/test_http.py @@ -233,7 +233,7 @@ def configure(self, chain_n): self.recieved_chain = deproxy.MessageChain.empty() self.client.clear() - self.client.set_request(self.current_chain.request) + self.client.set_request(self.current_chain) def recieved_response(self, response): # A lot of clients running, dont raise asyncore.ExitNow directly From 27e39a61e042de62f3aa295ecfe5341e00668bd6 Mon Sep 17 00:00:00 2001 From: Vladislav Tsendrovskii Date: Thu, 15 Mar 2018 07:39:58 +0300 Subject: [PATCH 05/12] fixes after review --- tempesta_fw/t/functional/helpers/chains.py | 56 ++++++------------- .../long_body/test_response_wrong_length.py | 4 +- 2 files changed, 20 insertions(+), 40 deletions(-) diff --git a/tempesta_fw/t/functional/helpers/chains.py b/tempesta_fw/t/functional/helpers/chains.py index 3fcceae59a..fa2e104c00 100644 --- a/tempesta_fw/t/functional/helpers/chains.py +++ b/tempesta_fw/t/functional/helpers/chains.py @@ -43,16 +43,25 @@ def response_500(): response = deproxy.Response.create(status=500, headers=headers) return response -def response_502(): - date = deproxy.HttpMessage.date_time_string() - headers = [ - 'Date: %s' % date, - 'Content-Length: 0', - 'Connection: keep-alive' - ] - response = deproxy.Response.create(status=502, headers=headers) - return response +def response_403(date=None, connection=None): + if date is None: + date = deproxy.HttpMessage.date_time_string() + headers = ['Content-Length: 0'] + if connection != None: + headers.append('Connection: %s' % connection) + return deproxy.Response.create(status=403, headers=headers, + date=date, body='') + +def response_400(date=None): + if date is None: + date = deproxy.HttpMessage.date_time_string() + resp = deproxy.Response.create(status=400, + headers=['Content-Length: 0', + 'Connection: keep-alive'], + date=date, + body='') + return resp def base(uri='/', method='GET', forward=True, date=None): """Base message chain. Looks like simple Curl request to Tempesta and @@ -217,35 +226,6 @@ def base(uri='/', method='GET', forward=True, date=None): server_response=backend_resp) return copy.copy(base_chain) -def response_403(date=None, connection=None): - if date is None: - date = deproxy.HttpMessage.date_time_string() - if connection is None: - resp = deproxy.Response.create(status=403, - headers=['Content-Length: 0'], - date=date, - body='') - else: - resp = deproxy.Response.create(status=403, - headers=[ - 'Content-Length: 0', - 'Connection: %s' % connection - ], - date=date, - body='') - return resp - -def response_400(date=None): - if date is None: - date = deproxy.HttpMessage.date_time_string() - resp = deproxy.Response.create(status=400, - headers=['Content-Length: 0', - 'Connection: keep-alive'], - date=date, - body='') - return resp - - def base_chunked(uri='/'): """Same as chains.base(), but returns a copy of message chain with chunked body. diff --git a/tempesta_fw/t/functional/long_body/test_response_wrong_length.py b/tempesta_fw/t/functional/long_body/test_response_wrong_length.py index 33c8812c96..ada8075900 100644 --- a/tempesta_fw/t/functional/long_body/test_response_wrong_length.py +++ b/tempesta_fw/t/functional/long_body/test_response_wrong_length.py @@ -148,7 +148,7 @@ def __init__(self, *args, **kwargs): "%i" % (length - 1)) base[0].server_response.build_message() - base[0].response = chains.response_502() + base[0].response = chains.make_502_expected() self.message_chains = [base[0]] self.cookies = [] @@ -165,7 +165,7 @@ def __init__(self, *args, **kwargs): base[0].server_response.headers['Content-Length'] = "invalid" base[0].server_response.build_message() - base[0].response = chains.response_502() + base[0].response = chains.make_502_expected() self.message_chains = [base[0]] self.cookies = [] From dcbf6b5fa6efd5656a75d87045233a910c6e3429 Mon Sep 17 00:00:00 2001 From: Vladislav Tsendrovskii Date: Thu, 15 Mar 2018 18:00:34 +0300 Subject: [PATCH 06/12] move saving config to file to test --- tempesta_fw/t/functional/helpers/control.py | 17 ++++++++--------- tempesta_fw/t/functional/helpers/wrk.py | 4 ++-- .../t/functional/long_body/test_long_request.py | 5 +---- 3 files changed, 11 insertions(+), 15 deletions(-) diff --git a/tempesta_fw/t/functional/helpers/control.py b/tempesta_fw/t/functional/helpers/control.py index 9eb09c41af..1f1fc84a64 100644 --- a/tempesta_fw/t/functional/helpers/control.py +++ b/tempesta_fw/t/functional/helpers/control.py @@ -132,23 +132,22 @@ def __init__(self, threads=-1, uri='/', ssl=False): '/../wrk/']) self.copy_script = True - def set_script(self, script, need_copy=True): + def set_script(self, script, content=None): self.script = script + ".lua" - self.copy_script = need_copy - - def append_script_option(self): - if not self.script: - return - script_path = self.workdir + "/" + self.script - - if self.copy_script: + if content == None: local_path = ''.join([self.local_scriptdir, self.script]) local_script_path = os.path.abspath(local_path) assert os.path.isfile(local_script_path), \ 'No script found: %s !' % local_script_path f = open(local_script_path, 'r') self.files.append((self.script, f.read())) + else: + self.node.copy_file(self.script, content) + def append_script_option(self): + if not self.script: + return + script_path = self.workdir + "/" + self.script self.options.append('-s %s' % script_path) def form_command(self): diff --git a/tempesta_fw/t/functional/helpers/wrk.py b/tempesta_fw/t/functional/helpers/wrk.py index d7b0610f8a..4db897fee1 100644 --- a/tempesta_fw/t/functional/helpers/wrk.py +++ b/tempesta_fw/t/functional/helpers/wrk.py @@ -30,7 +30,7 @@ def add_header(self, header_name, header_value): def set_body(self, body): self.body = body - def make_config(self, filename): + def make_config(self): """ Generate config and write it to file """ config = "" config += "wrk.method = \"%s\"\n" % self.request_type @@ -42,4 +42,4 @@ def make_config(self, filename): config += " [\"%s\"] = \"%s\",\n" % (name, value) config += "},\n" config += "wrk.body = \"%s\",\n" % self.__luaencode(self.body) - remote.client.copy_file(filename, config) + return config diff --git a/tempesta_fw/t/functional/long_body/test_long_request.py b/tempesta_fw/t/functional/long_body/test_long_request.py index 85db72dd6d..0f051c4190 100644 --- a/tempesta_fw/t/functional/long_body/test_long_request.py +++ b/tempesta_fw/t/functional/long_body/test_long_request.py @@ -23,11 +23,8 @@ def create_clients_with_body(self, length): self.generator = wrk.ScriptGenerator() self.generator.set_body(body_generator.generate_body(length)) - filename = self.script + ".lua" - self.generator.make_config(filename) - self.wrk = control.Wrk() - self.wrk.set_script(self.script, need_copy=False) + self.wrk.set_script(self.script, content=self.generator.make_config()) self.clients = [self.wrk] From f2b6bdf2377cfd71792c1d1a0753b4b420870c99 Mon Sep 17 00:00:00 2001 From: Ivan Koveshnikov Date: Fri, 16 Mar 2018 00:43:03 +0500 Subject: [PATCH 07/12] func tests: remove extra commas unwanded by wrk Fix warning: /tmp/client/request_1M.lua: /tmp/client/request_1M.lua:5: unexpected symbol near '=' --- tempesta_fw/t/functional/helpers/wrk.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tempesta_fw/t/functional/helpers/wrk.py b/tempesta_fw/t/functional/helpers/wrk.py index 4db897fee1..6557094eda 100644 --- a/tempesta_fw/t/functional/helpers/wrk.py +++ b/tempesta_fw/t/functional/helpers/wrk.py @@ -40,6 +40,6 @@ def make_config(self): name = self.__luaencode(header[0]) value = self.__luaencode(header[1]) config += " [\"%s\"] = \"%s\",\n" % (name, value) - config += "},\n" - config += "wrk.body = \"%s\",\n" % self.__luaencode(self.body) + config += "}\n" + config += "wrk.body = \"%s\"\n" % self.__luaencode(self.body) return config From fbe7b9c6b1fe2bdb27191dd0822f9f9353e086e4 Mon Sep 17 00:00:00 2001 From: Vladislav Tsendrovskii Date: Thu, 15 Mar 2018 23:58:30 +0300 Subject: [PATCH 08/12] Fixes after review --- .../health_monitoring/test_health_monitor.py | 2 +- tempesta_fw/t/functional/helpers/chains.py | 13 +++++++------ tempesta_fw/t/functional/helpers/deproxy.py | 4 ++-- .../long_body/test_request_wrong_length.py | 16 ++++++++-------- .../t/functional/msg_sequence/test_pairing.py | 2 +- .../t/functional/regression/test_shutdown.py | 2 +- tempesta_fw/t/functional/testers/functional.py | 6 +++++- 7 files changed, 25 insertions(+), 20 deletions(-) diff --git a/tempesta_fw/t/functional/health_monitoring/test_health_monitor.py b/tempesta_fw/t/functional/health_monitoring/test_health_monitor.py index f9b3a4ec0f..5759334a39 100644 --- a/tempesta_fw/t/functional/health_monitoring/test_health_monitor.py +++ b/tempesta_fw/t/functional/health_monitoring/test_health_monitor.py @@ -29,7 +29,7 @@ def prepare(self): self.tester.current_chain = copy.copy(self.chain) self.tester.recieved_chain = deproxy.MessageChain.empty() self.client.clear() - self.client.set_request(self.tester.current_chain.request) + self.client.set_request(self.tester.current_chain) def check_transition(self, messages): expected = None diff --git a/tempesta_fw/t/functional/helpers/chains.py b/tempesta_fw/t/functional/helpers/chains.py index fa2e104c00..5b3caa89b4 100644 --- a/tempesta_fw/t/functional/helpers/chains.py +++ b/tempesta_fw/t/functional/helpers/chains.py @@ -53,14 +53,15 @@ def response_403(date=None, connection=None): return deproxy.Response.create(status=403, headers=headers, date=date, body='') -def response_400(date=None): +def response_400(date=None, connection=None): if date is None: date = deproxy.HttpMessage.date_time_string() - resp = deproxy.Response.create(status=400, - headers=['Content-Length: 0', - 'Connection: keep-alive'], - date=date, - body='') + headers = ['Content-Length: 0'] + if connection != None: + headers.append('Connection: %s' % connection) + + resp = deproxy.Response.create(status=400, headers=headers, + date=date, body='') return resp def base(uri='/', method='GET', forward=True, date=None): diff --git a/tempesta_fw/t/functional/helpers/deproxy.py b/tempesta_fw/t/functional/helpers/deproxy.py index d3f8b2db88..16e26e4f8a 100644 --- a/tempesta_fw/t/functional/helpers/deproxy.py +++ b/tempesta_fw/t/functional/helpers/deproxy.py @@ -547,11 +547,11 @@ def handle_read(self): self.response_buffer = self.response_buffer[len(response.msg):] except IncompliteMessage: return - except ParseError: + except ParseError as err: tf_cfg.dbg(4, ('Deproxy: Client: Can\'t parse message\n' '<<<<<\n%s>>>>>' % self.response_buffer)) - raise + raise err if len(self.response_buffer) > 0: # TODO: take care about pipelined case raise ParseError('Garbage after response end:\n```\n%s\n```\n' % \ diff --git a/tempesta_fw/t/functional/long_body/test_request_wrong_length.py b/tempesta_fw/t/functional/long_body/test_request_wrong_length.py index a21735c40c..6e1ce7c79e 100644 --- a/tempesta_fw/t/functional/long_body/test_request_wrong_length.py +++ b/tempesta_fw/t/functional/long_body/test_request_wrong_length.py @@ -25,14 +25,14 @@ def req_body_length(base, length): base.request.build_message() return base -def generate_chain(method='GET', expect_403=False): +def generate_chain(method='GET', expect_400=False, connection=None): base = chains.base(method=method) chain = tester.BadLengthMessageChain(request=base.request, expected_responses=[base.response], forwarded_request=base.fwd_request, server_response=base.server_response) - if expect_403: - chain.responses.append(chains.response_403()) + if expect_400: + chain.responses.append(chains.response_400(connection=connection)) return chain class TesterCorrectBodyLength(tester.BadLengthDeproxy): @@ -50,13 +50,13 @@ def __init__(self, *args, **kwargs): class TesterMissingBodyLength(TesterCorrectBodyLength): """ Tester """ def create_base(self): - base = generate_chain(method='PUT', expect_403=True) + base = generate_chain(method='PUT', expect_400=True) return (base, None) class TesterSmallBodyLength(TesterCorrectBodyLength): """ Tester """ def create_base(self): - base = generate_chain(method='PUT', expect_403=True) + base = generate_chain(method='PUT', expect_400=True) return (base, len(base.request.body) - 15) class TesterDuplicateBodyLength(deproxy.Deproxy): @@ -70,7 +70,7 @@ def __init__(self, *args, **kwargs): base.fwd_request = deproxy.Request() - base.response = chains.response_403(connection='keep-alive') + base.response = chains.response_400(connection='keep-alive') self.message_chains = [base] self.cookies = [] @@ -81,7 +81,7 @@ def __init__(self, *args, **kwargs): base = chains.base(method='PUT') base.request.headers['Content-Length'] = 'invalid' base.request.build_message() - base.response = chains.response_400() + base.response = chains.response_400(connection='keep-alive') base.fwd_request = deproxy.Request() self.message_chains = [base] self.cookies = [] @@ -92,7 +92,7 @@ def second_length(self, content_length): return "%i" % (len - 1) def expected_response(self): - return chains.response_400() + return chains.response_400(connection='keep-alive') def __init__(self, *args, **kwargs): deproxy.Deproxy.__init__(self, *args, **kwargs) diff --git a/tempesta_fw/t/functional/msg_sequence/test_pairing.py b/tempesta_fw/t/functional/msg_sequence/test_pairing.py index 5649e68a98..9eda7c41c3 100644 --- a/tempesta_fw/t/functional/msg_sequence/test_pairing.py +++ b/tempesta_fw/t/functional/msg_sequence/test_pairing.py @@ -66,7 +66,7 @@ def send_reqs(self, req_n): for i in range(b_req, e_req): self.client.clear() - self.client.set_request(self.message_chains[i].request) + self.client.set_request(self.message_chains[i]) while self.client.request_buffer: self.loop(timeout=0.1) diff --git a/tempesta_fw/t/functional/regression/test_shutdown.py b/tempesta_fw/t/functional/regression/test_shutdown.py index e050d9c66d..2c127d8b26 100644 --- a/tempesta_fw/t/functional/regression/test_shutdown.py +++ b/tempesta_fw/t/functional/regression/test_shutdown.py @@ -100,7 +100,7 @@ def run(self): self.recieved_chain = deproxy.MessageChain.empty() for client in self.clients: client.clear() - client.set_request(self.current_chain.request) + client.set_request(self.current_chain) self.loop() def close_all(self): diff --git a/tempesta_fw/t/functional/testers/functional.py b/tempesta_fw/t/functional/testers/functional.py index d27b7235c4..1c1e408bee 100644 --- a/tempesta_fw/t/functional/testers/functional.py +++ b/tempesta_fw/t/functional/testers/functional.py @@ -3,6 +3,7 @@ import copy import asyncore from helpers import tf_cfg, control, tempesta, deproxy, stateful +from helpers.deproxy import ParseError __author__ = 'Tempesta Technologies, Inc.' __copyright__ = 'Copyright (C) 2017 Tempesta Technologies, Inc.' @@ -146,7 +147,10 @@ def generic_test_routine(self, tempesta_defconfig, message_chains): self.tester.start() tf_cfg.dbg(3, "\tStarting completed") - self.tester.run() + try: + self.tester.run() + except ParseError as err: + self.assertTrue(False, msg=str(type(err))) self.tempesta.get_stats() self.assert_tempesta() From 3a27d27642432d5dd4f9571d4a3e78fab740031c Mon Sep 17 00:00:00 2001 From: Vladislav Tsendrovskii Date: Fri, 16 Mar 2018 00:08:45 +0300 Subject: [PATCH 09/12] Fix handling asyncore and parsing error --- tempesta_fw/t/functional/helpers/deproxy.py | 21 +++++++++++++------ .../long_body/test_response_wrong_length.py | 2 +- .../t/functional/testers/functional.py | 2 +- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/tempesta_fw/t/functional/helpers/deproxy.py b/tempesta_fw/t/functional/helpers/deproxy.py index 16e26e4f8a..fe22ef6ddf 100644 --- a/tempesta_fw/t/functional/helpers/deproxy.py +++ b/tempesta_fw/t/functional/helpers/deproxy.py @@ -25,7 +25,6 @@ class ParseError(Exception): class IncompliteMessage(ParseError): pass - class HeaderCollection(object): """ A collection class for HTTP Headers. This class combines aspects of a list @@ -547,11 +546,11 @@ def handle_read(self): self.response_buffer = self.response_buffer[len(response.msg):] except IncompliteMessage: return - except ParseError as err: + except ParseError: tf_cfg.dbg(4, ('Deproxy: Client: Can\'t parse message\n' '<<<<<\n%s>>>>>' % self.response_buffer)) - raise err + raise if len(self.response_buffer) > 0: # TODO: take care about pipelined case raise ParseError('Garbage after response end:\n```\n%s\n```\n' % \ @@ -573,7 +572,10 @@ def handle_write(self): def handle_error(self): _, v, _ = sys.exc_info() - error.bug('\tDeproxy: Client: %s' % v) + if type(v) == ParseError or type(v) == AssertionError: + raise v + else: + error.bug('\tDeproxy: Client: %s' % v) @@ -685,14 +687,21 @@ def handle_accept(self): self.connections.append(handler) assert len(self.connections) <= self.conns_n, \ ('Too lot connections, expect %d, got %d' - & (self.conns_n, len(self.connections))) + % (self.conns_n, len(self.connections))) + + def handle_read_event(self): + asyncore.dispatcher.handle_read_event(self) def active_conns_n(self): return len(self.connections) def handle_error(self): _, v, _ = sys.exc_info() - raise Exception('\tDeproxy: Server %s:%d: %s' % (self.ip, self.port, v)) + if type(v) == AssertionError: + raise v + else: + raise Exception('\tDeproxy: Server %s:%d: %s' % \ + (self.ip, self.port, type(v))) def handle_close(self): self.stop() diff --git a/tempesta_fw/t/functional/long_body/test_response_wrong_length.py b/tempesta_fw/t/functional/long_body/test_response_wrong_length.py index ada8075900..49ebc17fdb 100644 --- a/tempesta_fw/t/functional/long_body/test_response_wrong_length.py +++ b/tempesta_fw/t/functional/long_body/test_response_wrong_length.py @@ -104,7 +104,7 @@ def __init__(self, *args, **kwargs): base[0].server_response.headers.add('Content-Length', "%i" % base[1]) base[0].server_response.build_message() - base[0].response = chains.response_500() + base[0].response = chains.make_502_expected() self.message_chains = [base[0]] self.cookies = [] diff --git a/tempesta_fw/t/functional/testers/functional.py b/tempesta_fw/t/functional/testers/functional.py index 1c1e408bee..7079a9f09e 100644 --- a/tempesta_fw/t/functional/testers/functional.py +++ b/tempesta_fw/t/functional/testers/functional.py @@ -150,7 +150,7 @@ def generic_test_routine(self, tempesta_defconfig, message_chains): try: self.tester.run() except ParseError as err: - self.assertTrue(False, msg=str(type(err))) + self.assertTrue(False, msg=err) self.tempesta.get_stats() self.assert_tempesta() From c5db882447cd7368a99dbd29d606b6e257bee03c Mon Sep 17 00:00:00 2001 From: Vladislav Tsendrovskii Date: Fri, 16 Mar 2018 01:23:37 +0300 Subject: [PATCH 10/12] handle close --- tempesta_fw/t/functional/helpers/deproxy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tempesta_fw/t/functional/helpers/deproxy.py b/tempesta_fw/t/functional/helpers/deproxy.py index fe22ef6ddf..5478ed05f9 100644 --- a/tempesta_fw/t/functional/helpers/deproxy.py +++ b/tempesta_fw/t/functional/helpers/deproxy.py @@ -704,7 +704,7 @@ def handle_error(self): (self.ip, self.port, type(v))) def handle_close(self): - self.stop() + self.close() #------------------------------------------------------------------------------- From 4c9c77a9e1157dbcc6d6f59daeb789525758140a Mon Sep 17 00:00:00 2001 From: Vladislav Tsendrovskii Date: Fri, 16 Mar 2018 12:01:35 +0300 Subject: [PATCH 11/12] Don't measure number of connections during tests with invalid response --- .../long_body/test_response_wrong_length.py | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/tempesta_fw/t/functional/long_body/test_response_wrong_length.py b/tempesta_fw/t/functional/long_body/test_response_wrong_length.py index 49ebc17fdb..f7ff1e79e6 100644 --- a/tempesta_fw/t/functional/long_body/test_response_wrong_length.py +++ b/tempesta_fw/t/functional/long_body/test_response_wrong_length.py @@ -49,12 +49,33 @@ def generate_chain_204(method='GET'): base.server_response.body = "" return base +class InvalidResponseServer(deproxy.Server): + + def __stop_server(self): + deproxy.Server.__stop_server(self) + assert len(self.connections) <= self.conns_n, \ + ('Too lot connections, expect %d, got %d' + % (self.conns_n, len(self.connections))) + + def handle_accept(self): + pair = self.accept() + if pair is not None: + sock, _ = pair + handler = deproxy.ServerConnection(self.tester, server=self, + sock=sock, + keep_alive=self.keep_alive) + self.connections.append(handler) + class TesterCorrectEmptyBodyLength(deproxy.Deproxy): """ Tester """ def create_base(self): base = generate_chain_200(method='GET') return (base, len(base.response.body)) + def recieved_response(self, response): + """Client received response for its request.""" + self.recieved_chain.response = response + def __init__(self, *args, **kwargs): deproxy.Deproxy.__init__(self, *args, **kwargs) base = self.create_base() @@ -128,7 +149,7 @@ def __init__(self, *args, **kwargs): base[0].server_response.headers.add('Content-Length', cl) base[0].server_response.build_message() - base[0].response = chains.response_500() + base[0].response = chains.make_502_expected() self.message_chains = [base[0]] self.cookies = [] @@ -178,6 +199,10 @@ class ResponseCorrectEmptyBodyLength(functional.FunctionalTest): """ Correct body length """ config = 'cache 0;\nblock_action error reply;\nblock_action attack reply;\n' + def create_servers(self): + port = tempesta.upstream_port_start_from() + self.servers = [InvalidResponseServer(port=port)] + def create_client(self): self.client = deproxy.Client() @@ -220,7 +245,7 @@ def assert_tempesta(self): msg = 'Tempesta have errors in processing HTTP %s.' self.assertEqual(self.tempesta.stats.cl_msg_parsing_errors, 0, msg=(msg % 'requests')) - self.assertEqual(self.tempesta.stats.srv_msg_parsing_errors, 1, + self.assertEqual(self.tempesta.stats.srv_msg_parsing_errors, 0, msg=(msg % 'responses')) self.assertEqual(self.tempesta.stats.cl_msg_other_errors, 0, msg=(msg % 'requests')) From e2a06785ed27e256c8a031c2e1a17316275c3906 Mon Sep 17 00:00:00 2001 From: Vladislav Tsendrovskii Date: Fri, 16 Mar 2018 12:26:58 +0300 Subject: [PATCH 12/12] fix TestInvalidResponse --- .../t/functional/regression/test_invalid.py | 42 +++++++++++++++---- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/tempesta_fw/t/functional/regression/test_invalid.py b/tempesta_fw/t/functional/regression/test_invalid.py index b6f6058a2f..b31ce70fc7 100644 --- a/tempesta_fw/t/functional/regression/test_invalid.py +++ b/tempesta_fw/t/functional/regression/test_invalid.py @@ -3,29 +3,57 @@ from __future__ import print_function import unittest from testers import functional -from helpers import chains, deproxy +from helpers import chains, deproxy, tempesta +from long_body import test_response_wrong_length + __author__ = 'Tempesta Technologies, Inc.' __copyright__ = 'Copyright (C) 2017 Tempesta Technologies, Inc.' __license__ = 'GPL2' +class TesterInvalidResponse(deproxy.Deproxy): + + def recieved_response(self, response): + """Client received response for its request.""" + self.recieved_chain.response = response + class TestInvalidResponse(functional.FunctionalTest): config = ( 'cache 0;\n' ) - @unittest.expectedFailure + def assert_tempesta(self): + """ Assert that tempesta had no errors during test. """ + msg = 'Tempesta have errors in processing HTTP %s.' + self.assertEqual(self.tempesta.stats.cl_msg_parsing_errors, 0, + msg=(msg % 'requests')) + self.assertEqual(self.tempesta.stats.srv_msg_parsing_errors, 1, + msg=(msg % 'responses')) + if not self.tfw_clnt_msg_otherr: + self.assertEqual(self.tempesta.stats.cl_msg_other_errors, 0, + msg=(msg % 'requests')) + self.assertEqual(self.tempesta.stats.srv_msg_other_errors, 0, + msg=(msg % 'responses')) + + + def create_tester(self): + self.tester = TesterInvalidResponse(self.client, self.servers) + + def create_servers(self): + port = tempesta.upstream_port_start_from() + srv = test_response_wrong_length.InvalidResponseServer(port=port) + self.servers = [srv] + def test_204_with_body(self): chain = chains.proxy() - for msg in (chain.server_response, chain.response): - msg.status = '204' - msg.update() + chain.server_response.status = '204' + chain.server_response.build_message() + chain.response = chains.make_502_expected() self.generic_test_routine(self.config, [chain]) - @unittest.expectedFailure def test_no_crlf_before_body(self): chain = chains.proxy() chain.server_response.msg = chain.server_response.msg.replace('\r\n\r\n', '\r\n', 1) - chain.response = deproxy.Response() + chain.response = chains.make_502_expected() self.generic_test_routine(self.config, [chain])