Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Long body in responses and requests #897

Merged
merged 12 commits into from
Mar 16, 2018
Merged

Long body in responses and requests #897

merged 12 commits into from
Mar 16, 2018

Conversation

vladtcvs
Copy link
Contributor

fix #619

@vladtcvs vladtcvs changed the title Long body in responses and requests WIP: Long body in responses and requests Jan 26, 2018
@vladtcvs vladtcvs force-pushed the vlts-619 branch 3 times, most recently from 875f12d to 578fd88 Compare January 26, 2018 11:52
@vladtcvs vladtcvs changed the title WIP: Long body in responses and requests Long body in responses and requests Jan 26, 2018
Copy link
Contributor

@vankoven vankoven left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#619 can't be covered by a stress test, deproxy is required here. The issue is about https://tools.ietf.org/html/rfc7230#section-3.3.3 point 7.

I also don't sure that we have all points from RFC7230 Section-3.3.3 covered.

@@ -108,19 +108,71 @@ def set_user_agent(self, ua):
class Wrk(Client):
""" wrk - HTTP benchmark utility. """

class ScriptGenerator(object):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why you have decided to define class inside Wrk class? It's requred only in some cases, better to move the definition into separate file like it's done for tempesta.py and nginx.py which provide advanced control options for Tempesta and Nginx classes. I admit i'm not good in python and there can be much and much better implementations of current code, but i can't agree here.

block_action error reply;

block_action attack reply;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems a mistaken change: the file is used by end users, so never put any testing stuff here, especially, specific for your local installation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

Copy link
Contributor

@vankoven vankoven left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've tried to run the test and got errors like this:

======================================================================
ERROR: test_purge (cache.test_purge.TestPurge)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/mnt/Storage/Projects/tempesta/tempesta/tempesta_fw/t/functional/cache/test_purge.py", line 40, in test_purge
    self.generic_test_routine(self.config, self.chains())
  File "/mnt/Storage/Projects/tempesta/tempesta/tempesta_fw/t/functional/cache/test_purge.py", line 26, in chains
    chains.proxy(method='HEAD', uri=uri),
  File "/mnt/Storage/Projects/tempesta/tempesta/tempesta_fw/t/functional/helpers/chains.py", line 241, in proxy
    return base(forward=True, *args, **kwargs)
  File "/mnt/Storage/Projects/tempesta/tempesta/tempesta_fw/t/functional/helpers/chains.py", line 160, in base
    body=common_resp_body,
  File "/mnt/Storage/Projects/tempesta/tempesta/tempesta_fw/t/functional/helpers/deproxy.py", line 480, in create
    return Response(msg)
  File "/mnt/Storage/Projects/tempesta/tempesta/tempesta_fw/t/functional/helpers/deproxy.py", line 413, in __init__
    HttpMessage.__init__(self, *args, **kwargs)
  File "/mnt/Storage/Projects/tempesta/tempesta/tempesta_fw/t/functional/helpers/deproxy.py", line 211, in __init__
    self.parse_text(message_text, body_parsing)
  File "/mnt/Storage/Projects/tempesta/tempesta/tempesta_fw/t/functional/helpers/deproxy.py", line 216, in parse_text
    self.__parse(stream)
  File "/mnt/Storage/Projects/tempesta/tempesta/tempesta_fw/t/functional/helpers/deproxy.py", line 223, in __parse
    self.parse_body(stream)
  File "/mnt/Storage/Projects/tempesta/tempesta/tempesta_fw/t/functional/helpers/deproxy.py", line 454, in parse_body
    self.read_sized_body(stream)
  File "/mnt/Storage/Projects/tempesta/tempesta/tempesta_fw/t/functional/helpers/deproxy.py", line 285, in read_sized_body
    % (size, len(self.body))))
ParseError: Wrong body size: expect 138 but got 0!

======================================================================
ERROR: test_head (cache.test_cache_methods.TestCacheMethods)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/mnt/Storage/Projects/tempesta/tempesta/tempesta_fw/t/functional/cache/test_cache_methods.py", line 57, in test_head
    self.try_method('HEAD')
  File "/mnt/Storage/Projects/tempesta/tempesta/tempesta_fw/t/functional/cache/test_cache_methods.py", line 36, in try_method
    cache_alowed=(method in self.cacheable_methods))
  File "/mnt/Storage/Projects/tempesta/tempesta/tempesta_fw/t/functional/cache/test_cache_methods.py", line 30, in chain
    return chains.cache_repeated(self.messages, method=method, uri=uri)
  File "/mnt/Storage/Projects/tempesta/tempesta/tempesta_fw/t/functional/helpers/chains.py", line 244, in cache_repeated
    chains = [proxy(*args, **kwargs)]
  File "/mnt/Storage/Projects/tempesta/tempesta/tempesta_fw/t/functional/helpers/chains.py", line 241, in proxy
    return base(forward=True, *args, **kwargs)
  File "/mnt/Storage/Projects/tempesta/tempesta/tempesta_fw/t/functional/helpers/chains.py", line 160, in base
    body=common_resp_body,
  File "/mnt/Storage/Projects/tempesta/tempesta/tempesta_fw/t/functional/helpers/deproxy.py", line 480, in create
    return Response(msg)
  File "/mnt/Storage/Projects/tempesta/tempesta/tempesta_fw/t/functional/helpers/deproxy.py", line 413, in __init__
    HttpMessage.__init__(self, *args, **kwargs)
  File "/mnt/Storage/Projects/tempesta/tempesta/tempesta_fw/t/functional/helpers/deproxy.py", line 211, in __init__
    self.parse_text(message_text, body_parsing)
  File "/mnt/Storage/Projects/tempesta/tempesta/tempesta_fw/t/functional/helpers/deproxy.py", line 216, in parse_text
    self.__parse(stream)
  File "/mnt/Storage/Projects/tempesta/tempesta/tempesta_fw/t/functional/helpers/deproxy.py", line 223, in __parse
    self.parse_body(stream)
  File "/mnt/Storage/Projects/tempesta/tempesta/tempesta_fw/t/functional/helpers/deproxy.py", line 454, in parse_body
    self.read_sized_body(stream)
  File "/mnt/Storage/Projects/tempesta/tempesta/tempesta_fw/t/functional/helpers/deproxy.py", line 285, in read_sized_body
    % (size, len(self.body))))
ParseError: Wrong body size: expect 138 but got 0!

======================================================================
ERROR: test_scheduler (sched.test_http.HttpRules)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/mnt/Storage/Projects/tempesta/tempesta/tempesta_fw/t/functional/sched/test_http.py", line 109, in test_scheduler
    self.routine()
  File "/mnt/Storage/Projects/tempesta/tempesta/tempesta_fw/t/functional/sched/test_http.py", line 90, in routine
    tester.configure(i)
  File "/mnt/Storage/Projects/tempesta/tempesta/tempesta_fw/t/functional/sched/test_http.py", line 216, in configure
    self.client.set_request(self.current_chain.request)
  File "/mnt/Storage/Projects/tempesta/tempesta/tempesta_fw/t/functional/helpers/deproxy.py", line 507, in set_request
    self.request = request_chain.request
AttributeError: 'Request' object has no attribute 'request'

Please, run all tests before requesting pull. You're changing the core of test frame work, and it's great (framework itself not very good in some places). But it's not acceptable if your changes break existing tests.

def parse_body(self, stream):
""" RFC 7230 3.3.3 """
# 3.3.3 1
if self.method == "HEAD":
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is many cases when response may not have body: 302 responses, 200 responses to CONNECT and so on. I think that previous flag suit better in this case.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

independent flag can generate inconsistency. We don't have 302 and CONNECT tests now, and when we write such tests, we should add this cases here

@@ -491,6 +532,8 @@ def handle_read(self):
'<<<<<\n%s>>>>>'
% self.response_buffer))
raise
if len(self.response_buffer) > 0:
raise ParseError('Garbage after response end:\n```\n%s\n```\n' % self.response_buffer)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, now it's makes the job. But a either a TODO comment or rework is required here. Currently both client and response don't support message pipelining because of tests structure: messages from TempestaFW are validated one-by one (The test suite was developed before pipelining was supported). We may configure Client client with the full chain list and give it some instructions about when to use the pipelining. When the Server receives a new message it can save it to the right slot in chain list. Not all tests allow pipelining.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now we don't have pipelined functional test. So I've added "TODO"

headers = []
body = ""
filename = None
def __luaencode(self, value):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Method can be a simple function. But why it's actually required? It does just nothing.

Copy link
Contributor Author

@vladtcvs vladtcvs Feb 18, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should care about escaping, if we write test with " and so on in content

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now we don't have tests with content, which should be escaped. So now it does nothing

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")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If i'm not mistaken just setting

wrk = {
    method  = "GET",
    path    = "/",
    headers = {},
    body    = nil,
}

will make the same job for you. In the same time wrk docs says that request() function is heavy and should be avoided:
https://github.com/wg/wrk/blob/7594a95186ebdfa7cb35477a8a811f84e2a31b62/SCRIPTING#L81-L84

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed, but we need

wrk.method = "GET",
wrk.path = ....

instead of wrk = {...}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

class RequestTestBase(stress.StressTest):
""" Test long request """
config = "cache 0;\n"
root = "/tmp/long_body/"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is variable client.workdir in tf_cfg which points to /tmp/client by default. Use it as root dir for your directories and files.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

""" 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):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use remote.node.mkdir() instead: first it always care about on which node should it make the directory, secondly it already throws an exception for test suit on errors.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wrk scripts copy code fixed

@vladtcvs
Copy link
Contributor Author

problem with breaking other tests fixed

@vladtcvs vladtcvs force-pushed the vlts-619 branch 3 times, most recently from dbe0da0 to 9d3eb96 Compare February 19, 2018 20:47
@vladtcvs vladtcvs force-pushed the vlts-619 branch 5 times, most recently from c1cc4d9 to 485b41a Compare March 12, 2018 03:59
@@ -197,6 +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, connection=None):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is response_500() in the begging of the file, and response_403 here. Better to group code somehow.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

response = deproxy.Response.create(status=500, headers=headers)
return response

def response_502():
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There already is

def make_502_expected():
That is the difference?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's the same. fixed

date=date,
body='')
else:
resp = deproxy.Response.create(status=403,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy-paste. It's better to use variable to store list of headers and add Connection: header if connection is not None, then call deproxy.Response.create() only once

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

script_path = self.workdir + "/" + self.script

if self.copy_script:
local_path = ''.join([self.local_scriptdir, self.script])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrk is always called on the same node as test frame work. Why need to copy file? We can just reference it. Which is old behaviour.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's for universal work with scripts from wrk/ and generated scripts

@vladtcvs vladtcvs force-pushed the vlts-619 branch 2 times, most recently from b78b362 to a707e9c Compare March 15, 2018 11:48
Copy link
Contributor

@vankoven vankoven left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good to mere, but a couple of cleanups is required.


def create_content(self, length):
""" Create content file """
content = body_generator.generate_body(length)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's good to test a lot of different kind of response. I think, we even need to go further: we need to prepare a back end configuration that will be capable to respond to POST, to serve a different type of resources, small and big ones. That will be a great enhancement for tests, isn't it?
Wee need to create a separate issue for that for one of the next milestones. What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's a good idea to make backend more configurable

config += " [\"%s\"] = \"%s\",\n" % (name, value)
config += "},\n"
config += "wrk.body = \"%s\",\n" % self.__luaencode(self.body)
remote.client.copy_file(filename, config)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We've discussed this line in the chat, it should be fixed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

@@ -512,6 +552,10 @@ def handle_read(self):
'<<<<<\n%s>>>>>'
% self.response_buffer))
raise
if len(self.response_buffer) > 0:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please check with test_pairing.py tests, i expect problems here. Reach me in chat if you catch an error there, we'll discuss a solution.

generating lua config from test

Add 1k and 1M test

Test responses

add 1M response test
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
vladtcvs and others added 7 commits March 16, 2018 00:50
Add missing length tests

Close connection after response w/out content-length

Add tests for multiple or invalid content-length
Fix warning:
  /tmp/client/request_1M.lua: /tmp/client/request_1M.lua:5:
    unexpected symbol near '='
@vladtcvs vladtcvs merged commit 3e09b4f into master Mar 16, 2018
@vladtcvs vladtcvs deleted the vlts-619 branch March 16, 2018 12:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Static test for unlimited request and response bodies
3 participants