TUN-4049: Add component tests to assert logging behavior when running from terminal
This commit is contained in:
parent
d22b374208
commit
f23e33c082
|
@ -12,8 +12,25 @@ tunnel: "3d539f97-cd3a-4d8e-c33b-65e9099c7a8d"
|
||||||
credentials_file: "/Users/tunnel/.cloudflared/3d539f97-cd3a-4d8e-c33b-65e9099c7a8d.json"
|
credentials_file: "/Users/tunnel/.cloudflared/3d539f97-cd3a-4d8e-c33b-65e9099c7a8d.json"
|
||||||
classic_hostname: "classic-tunnel-component-tests.example.com"
|
classic_hostname: "classic-tunnel-component-tests.example.com"
|
||||||
origincert: "/Users/tunnel/.cloudflared/cert.pem"
|
origincert: "/Users/tunnel/.cloudflared/cert.pem"
|
||||||
|
ingress:
|
||||||
|
- hostname: named-tunnel-component-tests.example.com
|
||||||
|
service: http_status:200
|
||||||
|
- service: http_status:404
|
||||||
```
|
```
|
||||||
|
|
||||||
|
3. Route hostname to the tunnel. For the example config above, we can do that via
|
||||||
|
```
|
||||||
|
cloudflared tunnel route dns 3d539f97-cd3a-4d8e-c33b-65e9099c7a8d named-tunnel-component-tests.example.com
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Turn on linter
|
||||||
|
If you are using Visual Studio, follow https://code.visualstudio.com/docs/python/linting to turn on linter.
|
||||||
|
|
||||||
|
5. Turn on formatter
|
||||||
|
If you are using Visual Studio, follow https://code.visualstudio.com/docs/python/editing#_formatting
|
||||||
|
to turn on formatter and https://marketplace.visualstudio.com/items?itemName=cbrevik.toggle-format-on-save
|
||||||
|
to turn on format on save.
|
||||||
|
|
||||||
# How to run
|
# How to run
|
||||||
Specify path to config file via env var `COMPONENT_TESTS_CONFIG`. This is required.
|
Specify path to config file via env var `COMPONENT_TESTS_CONFIG`. This is required.
|
||||||
## All tests
|
## All tests
|
||||||
|
|
|
@ -4,11 +4,15 @@ import copy
|
||||||
import os
|
import os
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass, InitVar
|
||||||
|
|
||||||
from constants import METRICS_PORT
|
from constants import METRICS_PORT
|
||||||
|
|
||||||
|
from util import LOGGER
|
||||||
|
|
||||||
# frozen=True raises exception when assigning to fields. This emulates immutability
|
# frozen=True raises exception when assigning to fields. This emulates immutability
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class TunnelBaseConfig:
|
class TunnelBaseConfig:
|
||||||
no_autoupdate: bool = True
|
no_autoupdate: bool = True
|
||||||
|
@ -27,20 +31,41 @@ class NamedTunnelBaseConfig(TunnelBaseConfig):
|
||||||
# so we have to use default values here and check if they are set in __post_init__
|
# so we have to use default values here and check if they are set in __post_init__
|
||||||
tunnel: str = None
|
tunnel: str = None
|
||||||
credentials_file: str = None
|
credentials_file: str = None
|
||||||
|
ingress: list = None
|
||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
if self.tunnel is None:
|
if self.tunnel is None:
|
||||||
raise TypeError("Field tunnel is not set")
|
raise TypeError("Field tunnel is not set")
|
||||||
if self.credentials_file is None:
|
if self.credentials_file is None:
|
||||||
raise TypeError("Field credentials_file is not set")
|
raise TypeError("Field credentials_file is not set")
|
||||||
|
if self.ingress is None:
|
||||||
|
raise TypeError("Field ingress is not set")
|
||||||
|
|
||||||
def merge_config(self, additional):
|
def merge_config(self, additional):
|
||||||
config = super(NamedTunnelBaseConfig, self).merge_config(additional)
|
config = super(NamedTunnelBaseConfig, self).merge_config(additional)
|
||||||
config['tunnel'] = self.tunnel
|
config['tunnel'] = self.tunnel
|
||||||
config['credentials-file'] = self.credentials_file
|
config['credentials-file'] = self.credentials_file
|
||||||
|
# In some cases we want to override default ingress, such as in config tests
|
||||||
|
if 'ingress' not in config:
|
||||||
|
config['ingress'] = self.ingress
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class NamedTunnelConfig(NamedTunnelBaseConfig):
|
||||||
|
full_config: dict = None
|
||||||
|
additional_config: InitVar[dict] = {}
|
||||||
|
|
||||||
|
def __post_init__(self, additional_config):
|
||||||
|
# Cannot call set self.full_config because the class is frozen, instead, we can use __setattr__
|
||||||
|
# https://docs.python.org/3/library/dataclasses.html#frozen-instances
|
||||||
|
object.__setattr__(self, 'full_config',
|
||||||
|
self.merge_config(additional_config))
|
||||||
|
|
||||||
|
def get_url(self):
|
||||||
|
return "https://" + self.ingress[0]['hostname']
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class ClassicTunnelBaseConfig(TunnelBaseConfig):
|
class ClassicTunnelBaseConfig(TunnelBaseConfig):
|
||||||
hostname: str = None
|
hostname: str = None
|
||||||
|
@ -54,28 +79,44 @@ class ClassicTunnelBaseConfig(TunnelBaseConfig):
|
||||||
|
|
||||||
def merge_config(self, additional):
|
def merge_config(self, additional):
|
||||||
config = super(ClassicTunnelBaseConfig, self).merge_config(additional)
|
config = super(ClassicTunnelBaseConfig, self).merge_config(additional)
|
||||||
config['hostnamel'] = self.hostname
|
config['hostname'] = self.hostname
|
||||||
config['origincert'] = self.origincert
|
config['origincert'] = self.origincert
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class ClassicTunnelConfig(ClassicTunnelBaseConfig):
|
||||||
|
full_config: dict = None
|
||||||
|
additional_config: InitVar[dict] = {}
|
||||||
|
|
||||||
|
def __post_init__(self, additional_config):
|
||||||
|
# Cannot call set self.full_config because the class is frozen, instead, we can use __setattr__
|
||||||
|
# https://docs.python.org/3/library/dataclasses.html#frozen-instances
|
||||||
|
object.__setattr__(self, 'full_config',
|
||||||
|
self.merge_config(additional_config))
|
||||||
|
|
||||||
|
def get_url(self):
|
||||||
|
return "https://" + self.hostname
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class ComponentTestConfig:
|
class ComponentTestConfig:
|
||||||
cloudflared_binary: str
|
cloudflared_binary: str
|
||||||
named_tunnel_config: dict
|
named_tunnel_config: NamedTunnelConfig
|
||||||
classic_tunnel_config: dict
|
classic_tunnel_config: ClassicTunnelConfig
|
||||||
|
|
||||||
|
|
||||||
def build_config_from_env():
|
def build_config_from_env():
|
||||||
config_path = get_env("COMPONENT_TESTS_CONFIG")
|
config_path = get_env("COMPONENT_TESTS_CONFIG")
|
||||||
config_content = base64.b64decode(get_env("COMPONENT_TESTS_CONFIG_CONTENT")).decode('utf-8')
|
config_content = base64.b64decode(
|
||||||
|
get_env("COMPONENT_TESTS_CONFIG_CONTENT")).decode('utf-8')
|
||||||
config_yaml = yaml.safe_load(config_content)
|
config_yaml = yaml.safe_load(config_content)
|
||||||
|
|
||||||
credentials_file = get_env("COMPONENT_TESTS_CREDENTIALS_FILE")
|
credentials_file = get_env("COMPONENT_TESTS_CREDENTIALS_FILE")
|
||||||
write_file(credentials_file, config_yaml["credentials_file"])
|
write_file(credentials_file, config_yaml["credentials_file"])
|
||||||
|
|
||||||
origincert = get_env("COMPONENT_TESTS_ORIGINCERT")
|
origincert = get_env("COMPONENT_TESTS_ORIGINCERT")
|
||||||
write_file(origincert,config_yaml["origincert"])
|
write_file(origincert, config_yaml["origincert"])
|
||||||
|
|
||||||
write_file(config_content, config_path)
|
write_file(config_content, config_path)
|
||||||
|
|
||||||
|
|
|
@ -2,23 +2,34 @@ import os
|
||||||
import pytest
|
import pytest
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
from config import ComponentTestConfig, NamedTunnelBaseConfig, ClassicTunnelBaseConfig
|
from time import sleep
|
||||||
|
|
||||||
|
from config import ComponentTestConfig, NamedTunnelConfig, ClassicTunnelConfig
|
||||||
|
from constants import BACKOFF_SECS
|
||||||
from util import LOGGER
|
from util import LOGGER
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
def component_tests_config():
|
def component_tests_config():
|
||||||
config_file = os.getenv("COMPONENT_TESTS_CONFIG")
|
config_file = os.getenv("COMPONENT_TESTS_CONFIG")
|
||||||
if config_file is None:
|
if config_file is None:
|
||||||
raise Exception("Need to provide path to config file in COMPONENT_TESTS_CONFIG")
|
raise Exception(
|
||||||
|
"Need to provide path to config file in COMPONENT_TESTS_CONFIG")
|
||||||
with open(config_file, 'r') as stream:
|
with open(config_file, 'r') as stream:
|
||||||
config = yaml.safe_load(stream)
|
config = yaml.safe_load(stream)
|
||||||
LOGGER.info(f"component tests base config {config}")
|
LOGGER.info(f"component tests base config {config}")
|
||||||
base_named_tunnel_config = NamedTunnelBaseConfig(tunnel=config['tunnel'], credentials_file=config['credentials_file'])
|
|
||||||
base_classic_tunnel_config = ClassicTunnelBaseConfig(hostname=config['classic_hostname'], origincert=config['origincert'])
|
|
||||||
|
|
||||||
def _component_tests_config(extra_named_tunnel_config={}, extra_classic_tunnel_config={}):
|
def _component_tests_config(extra_named_tunnel_config={}, extra_classic_tunnel_config={}):
|
||||||
named_tunnel_config = base_named_tunnel_config.merge_config(extra_named_tunnel_config)
|
named_tunnel_config = NamedTunnelConfig(additional_config=extra_named_tunnel_config,
|
||||||
classic_tunnel_config = base_classic_tunnel_config.merge_config(extra_classic_tunnel_config)
|
tunnel=config['tunnel'], credentials_file=config['credentials_file'], ingress=config['ingress'])
|
||||||
|
classic_tunnel_config = ClassicTunnelConfig(
|
||||||
|
additional_config=extra_classic_tunnel_config, hostname=config['classic_hostname'], origincert=config['origincert'])
|
||||||
return ComponentTestConfig(config['cloudflared_binary'], named_tunnel_config, classic_tunnel_config)
|
return ComponentTestConfig(config['cloudflared_binary'], named_tunnel_config, classic_tunnel_config)
|
||||||
|
|
||||||
return _component_tests_config
|
return _component_tests_config
|
||||||
|
|
||||||
|
|
||||||
|
# This fixture is automatically called before each tests to make sure the previous cloudflared has been shutdown
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def wait_previous_cloudflared():
|
||||||
|
sleep(BACKOFF_SECS)
|
||||||
|
|
|
@ -1 +1,3 @@
|
||||||
METRICS_PORT = 51000
|
METRICS_PORT = 51000
|
||||||
|
MAX_RETRIES = 3
|
||||||
|
BACKOFF_SECS = 5
|
||||||
|
|
|
@ -1,2 +1,4 @@
|
||||||
pytest==6.2.2
|
pytest==6.2.2
|
||||||
pyyaml==5.4.1
|
pyyaml==5.4.1
|
||||||
|
requests==2.25.1
|
||||||
|
retrying==1.3.3
|
|
@ -1,6 +1,7 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
from util import start_cloudflared
|
from util import start_cloudflared
|
||||||
|
|
||||||
|
|
||||||
class TestConfig:
|
class TestConfig:
|
||||||
# tmp_path is a fixture provides a temporary directory unique to the test invocation
|
# tmp_path is a fixture provides a temporary directory unique to the test invocation
|
||||||
def test_validate_ingress_rules(self, tmp_path, component_tests_config):
|
def test_validate_ingress_rules(self, tmp_path, component_tests_config):
|
||||||
|
@ -32,23 +33,25 @@ class TestConfig:
|
||||||
}
|
}
|
||||||
component_tests_config = component_tests_config(extra_config)
|
component_tests_config = component_tests_config(extra_config)
|
||||||
validate_args = ["ingress", "validate"]
|
validate_args = ["ingress", "validate"]
|
||||||
pre_args = ["tunnel"]
|
_ = start_cloudflared(tmp_path, component_tests_config, validate_args)
|
||||||
validate = start_cloudflared(tmp_path, component_tests_config, validate_args, pre_args)
|
|
||||||
assert validate.returncode == 0, "failed to validate ingress" + validate.stderr.decode("utf-8")
|
|
||||||
|
|
||||||
self.match_rule(tmp_path, component_tests_config, "http://example.com/index.html", 1)
|
|
||||||
self.match_rule(tmp_path, component_tests_config, "https://example.com/index.html", 1)
|
|
||||||
self.match_rule(tmp_path, component_tests_config, "https://api.example.com/login", 2)
|
|
||||||
self.match_rule(tmp_path, component_tests_config, "https://wss.example.com", 3)
|
|
||||||
self.match_rule(tmp_path, component_tests_config, "https://ssh.example.com", 4)
|
|
||||||
self.match_rule(tmp_path, component_tests_config, "https://api.example.com", 5)
|
|
||||||
|
|
||||||
|
self.match_rule(tmp_path, component_tests_config,
|
||||||
|
"http://example.com/index.html", 1)
|
||||||
|
self.match_rule(tmp_path, component_tests_config,
|
||||||
|
"https://example.com/index.html", 1)
|
||||||
|
self.match_rule(tmp_path, component_tests_config,
|
||||||
|
"https://api.example.com/login", 2)
|
||||||
|
self.match_rule(tmp_path, component_tests_config,
|
||||||
|
"https://wss.example.com", 3)
|
||||||
|
self.match_rule(tmp_path, component_tests_config,
|
||||||
|
"https://ssh.example.com", 4)
|
||||||
|
self.match_rule(tmp_path, component_tests_config,
|
||||||
|
"https://api.example.com", 5)
|
||||||
|
|
||||||
# This is used to check that the command tunnel ingress url <url> matches rule number <rule_num>. Note that rule number uses 1-based indexing
|
# This is used to check that the command tunnel ingress url <url> matches rule number <rule_num>. Note that rule number uses 1-based indexing
|
||||||
|
|
||||||
def match_rule(self, tmp_path, component_tests_config, url, rule_num):
|
def match_rule(self, tmp_path, component_tests_config, url, rule_num):
|
||||||
args = ["ingress", "rule", url]
|
args = ["ingress", "rule", url]
|
||||||
pre_args = ["tunnel"]
|
match_rule = start_cloudflared(tmp_path, component_tests_config, args)
|
||||||
match_rule = start_cloudflared(tmp_path, component_tests_config, args, pre_args)
|
|
||||||
|
|
||||||
assert match_rule.returncode == 0, "failed to check rule" + match_rule.stderr.decode("utf-8")
|
|
||||||
assert f"Matched rule #{rule_num}" .encode() in match_rule.stdout
|
assert f"Matched rule #{rule_num}" .encode() in match_rule.stdout
|
|
@ -0,0 +1,92 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from util import start_cloudflared, wait_tunnel_ready, send_requests, LOGGER
|
||||||
|
|
||||||
|
|
||||||
|
class TestLogging:
|
||||||
|
# Rolling logger rotate log files after 1 MB
|
||||||
|
rotate_after_size = 1000 * 1000
|
||||||
|
default_log_file = "cloudflared.log"
|
||||||
|
expect_message = "Starting tunnel"
|
||||||
|
|
||||||
|
def test_logging_to_terminal(self, tmp_path, component_tests_config):
|
||||||
|
config = component_tests_config()
|
||||||
|
with start_cloudflared(tmp_path, config, new_process=True) as cloudflared:
|
||||||
|
wait_tunnel_ready()
|
||||||
|
self.assert_log_to_terminal(cloudflared)
|
||||||
|
|
||||||
|
def test_logging_to_file(self, tmp_path, component_tests_config):
|
||||||
|
log_file = tmp_path / self.default_log_file
|
||||||
|
extra_config = {
|
||||||
|
# Convert from pathlib.Path to str
|
||||||
|
"logfile": str(log_file),
|
||||||
|
}
|
||||||
|
config = component_tests_config(extra_config)
|
||||||
|
with start_cloudflared(tmp_path, config, new_process=True, capture_output=False):
|
||||||
|
wait_tunnel_ready()
|
||||||
|
self.assert_log_in_file(log_file)
|
||||||
|
self.assert_json_log(log_file)
|
||||||
|
|
||||||
|
def test_logging_to_dir(self, tmp_path, component_tests_config):
|
||||||
|
log_dir = tmp_path / "logs"
|
||||||
|
extra_config = {
|
||||||
|
"loglevel": "debug",
|
||||||
|
# Convert from pathlib.Path to str
|
||||||
|
"log-directory": str(log_dir),
|
||||||
|
}
|
||||||
|
config = component_tests_config(extra_config)
|
||||||
|
with start_cloudflared(tmp_path, config, new_process=True, capture_output=False):
|
||||||
|
wait_tunnel_ready()
|
||||||
|
self.assert_log_to_dir(config, log_dir)
|
||||||
|
|
||||||
|
def assert_log_to_terminal(self, cloudflared):
|
||||||
|
stderr = cloudflared.stderr.read(200)
|
||||||
|
# cloudflared logs the following when it first starts
|
||||||
|
# 2021-03-10T12:30:39Z INF Starting tunnel tunnelID=<tunnel ID>
|
||||||
|
assert self.expect_message.encode(
|
||||||
|
) in stderr, f"{stderr} doesn't contain {self.expect_message}"
|
||||||
|
|
||||||
|
def assert_log_in_file(self, file, expect_message=""):
|
||||||
|
with open(file, "r") as f:
|
||||||
|
log = f.read(200)
|
||||||
|
# cloudflared logs the following when it first starts
|
||||||
|
# {"level":"info","tunnelID":"<tunnel ID>","time":"2021-03-10T12:21:13Z","message":"Starting tunnel"}
|
||||||
|
assert self.expect_message in log, f"{log} doesn't contain {self.expect_message}"
|
||||||
|
|
||||||
|
def assert_json_log(self, file):
|
||||||
|
with open(file, "r") as f:
|
||||||
|
line = f.readline()
|
||||||
|
json_log = json.loads(line)
|
||||||
|
self.assert_in_json(json_log, "level")
|
||||||
|
self.assert_in_json(json_log, "time")
|
||||||
|
self.assert_in_json(json_log, "message")
|
||||||
|
|
||||||
|
def assert_in_json(self, j, key):
|
||||||
|
assert key in j, f"{key} is not in j"
|
||||||
|
|
||||||
|
def assert_log_to_dir(self, config, log_dir):
|
||||||
|
max_batches = 3
|
||||||
|
batch_requests = 1000
|
||||||
|
for _ in range(max_batches):
|
||||||
|
send_requests(config.named_tunnel_config.get_url(),
|
||||||
|
batch_requests, require_ok=False)
|
||||||
|
files = os.listdir(log_dir)
|
||||||
|
if len(files) == 2:
|
||||||
|
current_log_file_index = files.index(self.default_log_file)
|
||||||
|
current_file = log_dir / files[current_log_file_index]
|
||||||
|
stats = os.stat(current_file)
|
||||||
|
assert stats.st_size > 0
|
||||||
|
self.assert_json_log(current_file)
|
||||||
|
|
||||||
|
# One file is the current log file, the other is the rotated log file
|
||||||
|
rotated_log_file_index = 0 if current_log_file_index == 1 else 1
|
||||||
|
rotated_file = log_dir / files[rotated_log_file_index]
|
||||||
|
stats = os.stat(rotated_file)
|
||||||
|
assert stats.st_size > self.rotate_after_size
|
||||||
|
self.assert_log_in_file(rotated_file)
|
||||||
|
self.assert_json_log(current_file)
|
||||||
|
return
|
||||||
|
|
||||||
|
raise Exception(
|
||||||
|
f"Log file isn't rotated after sending {max_batches * batch_requests} requests")
|
|
@ -1,10 +1,17 @@
|
||||||
|
from contextlib import contextmanager
|
||||||
import logging
|
import logging
|
||||||
import os
|
import requests
|
||||||
|
from retrying import retry
|
||||||
import subprocess
|
import subprocess
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
|
from time import sleep
|
||||||
|
|
||||||
|
from constants import METRICS_PORT, MAX_RETRIES, BACKOFF_SECS
|
||||||
|
|
||||||
LOGGER = logging.getLogger(__name__)
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def write_config(path, config):
|
def write_config(path, config):
|
||||||
config_path = path / "config.yaml"
|
config_path = path / "config.yaml"
|
||||||
with open(config_path, 'w') as outfile:
|
with open(config_path, 'w') as outfile:
|
||||||
|
@ -12,15 +19,57 @@ def write_config(path, config):
|
||||||
return config_path
|
return config_path
|
||||||
|
|
||||||
|
|
||||||
def start_cloudflared(path, component_test_config, cfd_args, cfd_pre_args=[], classic=False):
|
def start_cloudflared(path, component_test_config, cfd_args=["run"], cfd_pre_args=["tunnel"], new_process=False, classic=False, capture_output=True):
|
||||||
if classic:
|
if classic:
|
||||||
config = component_test_config.classic_tunnel_config
|
config = component_test_config.classic_tunnel_config.full_config
|
||||||
else:
|
else:
|
||||||
config = component_test_config.named_tunnel_config
|
config = component_test_config.named_tunnel_config.full_config
|
||||||
config_path = write_config(path, config)
|
config_path = write_config(path, config)
|
||||||
cmd = [component_test_config.cloudflared_binary]
|
cmd = [component_test_config.cloudflared_binary]
|
||||||
cmd += cfd_pre_args
|
cmd += cfd_pre_args
|
||||||
cmd += ["--config", config_path]
|
cmd += ["--config", config_path]
|
||||||
cmd += cfd_args
|
cmd += cfd_args
|
||||||
LOGGER.info(f"Run cmd {cmd} with config {config}")
|
LOGGER.info(f"Run cmd {cmd} with config {config}")
|
||||||
return subprocess.run(cmd, capture_output=True)
|
if new_process:
|
||||||
|
return run_cloudflared_background(cmd, capture_output)
|
||||||
|
# By setting check=True, it will raise an exception if the process exits with non-zero exit code
|
||||||
|
return subprocess.run(cmd, check=True, capture_output=capture_output)
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def run_cloudflared_background(cmd, capture_output):
|
||||||
|
output = subprocess.PIPE if capture_output else subprocess.DEVNULL
|
||||||
|
try:
|
||||||
|
cfd = subprocess.Popen(cmd, stdout=output, stderr=output)
|
||||||
|
yield cfd
|
||||||
|
finally:
|
||||||
|
cfd.terminate()
|
||||||
|
|
||||||
|
|
||||||
|
@retry(stop_max_attempt_number=MAX_RETRIES, wait_fixed=BACKOFF_SECS * 1000)
|
||||||
|
def wait_tunnel_ready():
|
||||||
|
url = f'http://localhost:{METRICS_PORT}/ready'
|
||||||
|
send_requests(url, 1)
|
||||||
|
|
||||||
|
# In some cases we don't need to check response status, such as when sending batch requests to generate logs
|
||||||
|
|
||||||
|
|
||||||
|
def send_requests(url, count, require_ok=True):
|
||||||
|
errors = 0
|
||||||
|
with requests.Session() as s:
|
||||||
|
for _ in range(count):
|
||||||
|
ok = send_request(s, url, require_ok)
|
||||||
|
if not ok:
|
||||||
|
errors += 1
|
||||||
|
sleep(0.01)
|
||||||
|
if errors > 0:
|
||||||
|
LOGGER.warning(
|
||||||
|
f"{errors} out of {count} requests to {url} return non-200 status")
|
||||||
|
|
||||||
|
|
||||||
|
@retry(stop_max_attempt_number=MAX_RETRIES, wait_fixed=BACKOFF_SECS * 1000)
|
||||||
|
def send_request(session, url, require_ok):
|
||||||
|
resp = session.get(url, timeout=BACKOFF_SECS)
|
||||||
|
if require_ok:
|
||||||
|
assert resp.status_code == 200, f"{url} returned {resp}"
|
||||||
|
return True if resp.status_code == 200 else False
|
||||||
|
|
Loading…
Reference in New Issue