diff --git a/README.md b/README.md index a3c020bc4..62582f7f7 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ deproxy server, and workload tests should use wrk client and nginx server. - Host for testing framework: `Python2`, `python2-paramiko`, `python-configparser`, `python-subprocess32`, `wrk`, `ab`, `python-scapy`, -`python-cryptography`, `scapy-ssl_tls` (installed with `pip`) +`python-cryptography`, `scapy-ssl_tls` (installed with `pip`), `h2spec` - All hosts except previous one: `sftp-server` - Host for running TempestaFW: Linux kernel with Tempesta, TempestaFW sources, `systemtap`, `tcpdump`, `bc` @@ -83,6 +83,9 @@ deproxy server, and workload tests should use wrk client and nginx server. `ab` is Apache benchmark tool, that can be found in `apache2-utils` package in Debian or `httpd-tools` in CentOS. +`h2spec` is HTTP/2 conformance test suite. Can't be installed from package +manager and must be retrieved from [GitHub](https://github.com/summerwind/h2spec/releases/latest). + Unfortunately, CentOS does not have `python-subprocess32` package, but it can be downloaded from [CentOS CBS](https://cbs.centos.org/koji/buildinfo?buildID=10904) @@ -151,7 +154,7 @@ names available in PATH. #### Tempesta Section `ip` — IPv4/IPv6 address of the TempestaFW host in test network, as reachable -from the client and server hosts. +from the client and server hosts. `hostname`, `port`, `user` — address and credentials used to reach the host via SSH. If hostname is `localhost`, TempestaFW will be ran locally. diff --git a/framework/__init__.py b/framework/__init__.py index 95b6948e4..6e7220061 100644 --- a/framework/__init__.py +++ b/framework/__init__.py @@ -1,2 +1,3 @@ __all__ = ['client', 'deproxy_client', 'deproxy_manager', 'deproxy_server', - 'nginx_server', 'templates', 'tester', 'wrk_client', 'port_checks'] + 'nginx_server', 'templates', 'tester', 'wrk_client', 'port_checks', + 'external_client'] diff --git a/framework/client.py b/framework/client.py index 162de40b3..cf222b755 100644 --- a/framework/client.py +++ b/framework/client.py @@ -63,6 +63,9 @@ def set_uri(self, uri): They use file with list of uris instead. Don't force clients to use uri field. """ + if not uri: + self.uri = '' + return proto = 'https://' if self.ssl else 'http://' self.uri = ''.join([proto, self.server_addr, uri]) diff --git a/framework/external_client.py b/framework/external_client.py new file mode 100644 index 000000000..45f79f28a --- /dev/null +++ b/framework/external_client.py @@ -0,0 +1,30 @@ +from . import client + + +__author__ = 'Tempesta Technologies, Inc.' +__copyright__ = 'Copyright (C) 2020 Tempesta Technologies, Inc.' +__license__ = 'GPL2' + +class ExternalTester(client.Client): + """The class allows to run various 3d-party test suites or any programs + against Tempesta. Required properties of `client` definitions inside + `tester.TempestaTest` class: + - `type` - common attribute for all definitions. Must have value `external` + - `binary` - binary to run. The `binary` value is checked against tests + config file and alias from `Client` section can be used for that + `binary` value. Thus full path mustn't apper in test description just + in config file. + - `cmd_args` - initial list of command line arguments. Can be updated + in runtime via modifying `options` (list of strings) member. + """ + + def __init__(self, cmd_args, **kwargs): + client.Client.__init__(self, **kwargs) + self.options = [cmd_args] + + def form_command(self): + cmd = ' '.join([self.bin] + self.options) + return cmd + + def parse_out(self, stdout, stderr): + return True diff --git a/framework/tester.py b/framework/tester.py index de6483e7d..21f0908de 100644 --- a/framework/tester.py +++ b/framework/tester.py @@ -7,6 +7,8 @@ import framework.wrk_client as wrk_client import framework.deproxy_client as deproxy_client import framework.deproxy_manager as deproxy_manager +import framework.external_client as external_client + from framework.templates import fill_template, populate_properties import socket @@ -150,6 +152,14 @@ def __create_client_wrk(self, client, ssl): wrk.set_script(client['id']+"_script", content="") return wrk + def __create_client_external(self, client_descr): + cmd_args = fill_template(client_descr['cmd_args'], client_descr) + ext_client = external_client.ExternalTester(binary=client_descr['binary'], + cmd_args=cmd_args, + server_addr=None, + uri=None) + return ext_client + def __create_client(self, client): populate_properties(client) ssl = client.setdefault('ssl', False) @@ -167,6 +177,8 @@ def __create_client(self, client): self.__clients[cid].set_rps(client.get('rps', 0)) elif client['type'] == 'wrk': self.__clients[cid] = self.__create_client_wrk(client, ssl) + elif client['type'] == 'external': + self.__clients[cid] = self.__create_client_external(client) def __create_backend(self, server): srv = None diff --git a/h2/__init__.py b/h2/__init__.py new file mode 100644 index 000000000..92eb19c56 --- /dev/null +++ b/h2/__init__.py @@ -0,0 +1,3 @@ +__all__ = ['test_h2_specs'] + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/h2/test_h2_specs.py b/h2/test_h2_specs.py new file mode 100644 index 000000000..dc88f64fe --- /dev/null +++ b/h2/test_h2_specs.py @@ -0,0 +1,104 @@ +from helpers import tf_cfg +from framework import tester + +__author__ = 'Tempesta Technologies, Inc.' +__copyright__ = 'Copyright (C) 2020 Tempesta Technologies, Inc.' +__license__ = 'GPL2' + +NGINX_CONFIG = """ +pid ${pid}; +worker_processes auto; + +events { + worker_connections 1024; + use epoll; +} + +http { + keepalive_timeout ${server_keepalive_timeout}; + keepalive_requests ${server_keepalive_requests}; + sendfile on; + tcp_nopush on; + tcp_nodelay on; + + open_file_cache max=1000; + open_file_cache_valid 30s; + open_file_cache_min_uses 2; + open_file_cache_errors off; + + # [ debug | info | notice | warn | error | crit | alert | emerg ] + # Fully disable log errors. + error_log /dev/null emerg; + + # Disable access log altogether. + access_log off; + + server { + listen ${server_ip}:8000; + + location / { + return 200; + } + location /nginx_status { + stub_status on; + } + } +} +""" + +TEMPESTA_CONFIG = """ +listen 443 proto=h2; + +srv_group default { + server ${server_ip}:8000; +} +vhost default { + tls_certificate ${general_workdir}/tempesta.crt; + tls_certificate_key ${general_workdir}/tempesta.key; + + proxy_pass default; +} + +cache 0; + +""" + +class H2Spec(tester.TempestaTest): + '''Tests for h2 proto implementation. Run h2spec utility against Tempesta. + Simply check return code and warnings in system log for test errors. + ''' + + clients = [ + { + 'id' : 'h2spec', + 'type' : 'external', + 'binary' : 'h2spec', + 'ssl' : True, + 'cmd_args' : '-tkh ${tempesta_ip}' + }, + ] + + backends = [ + { + 'id' : 'nginx', + 'type' : 'nginx', + 'port' : '8000', + 'status_uri' : 'http://${server_ip}:8000/nginx_status', + 'config' : NGINX_CONFIG, + } + ] + + tempesta = { + 'config' : TEMPESTA_CONFIG, + } + + def test_h2_specs(self): + h2spec = self.get_client('h2spec') + # TODO #88: for now run only simple test to check http2 connectivity + # After all h2-related issues will be fixed, remove the line + h2spec.options.append('generic/4') + + self.start_all_servers() + self.start_tempesta() + self.start_all_clients() + self.wait_while_busy(h2spec) diff --git a/helpers/remote.py b/helpers/remote.py index f0061bc49..b3a0a6455 100644 --- a/helpers/remote.py +++ b/helpers/remote.py @@ -79,7 +79,8 @@ def run_cmd(self, cmd, timeout=DEFAULT_TIMEOUT, ignore_stderr=False, stderr=stderr_pipe, env=env_full) as p: try: stdout, stderr = p.communicate(timeout) - assert p.returncode == 0, "Return code is not 0." + assert p.returncode == 0, \ + "Cmd: '%s' return code is not 0 (%d)." % (cmd, p.returncode) except Exception as e: if not err_msg: err_msg = ("Error running command '%s' on %s" % @@ -221,7 +222,7 @@ def remove_file(self, filename): def wait_available(self): tf_cfg.dbg(3, '\tWaiting for %s node' % self.type) - timeout = float(tf_cfg.cfg.get(self.type, 'unavaliable_timeout')) + timeout = float(tf_cfg.cfg.get(self.type, 'unavaliable_timeout')) t0 = time.time() while True: t = time.time() diff --git a/helpers/tempesta.py b/helpers/tempesta.py index e331023e1..7146d2002 100644 --- a/helpers/tempesta.py +++ b/helpers/tempesta.py @@ -245,7 +245,8 @@ def __handle_tls(self, custom_cert): "Two or more certificates configured, please use custom_cert" \ " option in Tempesta configuration" cfg[k] = v - if not cfg.has_key('listen') or not 'https' in cfg['listen']: + if not cfg.has_key('listen') or not \ + any(proto in cfg['listen'] for proto in ['https', 'h2']): return cert_path, key_path = cfg['tls_certificate'], cfg['tls_certificate_key'] cgen = CertGenerator(cert_path, key_path, True) diff --git a/tests_config.ini.sample b/tests_config.ini.sample index 3dbe03479..c7d7313a9 100644 --- a/tests_config.ini.sample +++ b/tests_config.ini.sample @@ -78,6 +78,12 @@ ab = ab # wrk = wrk +# h2spec conformance testing tool for HTTP/2 implementation. +# +# ex.: h2spec = /home/user/bin/h2spec +# +h2spec = h2spec + [Tempesta] # Configuration for the host running TempestaFW. It may be configured to run