Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 0 additions & 11 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1439,17 +1439,6 @@ if(BUILD_TESTS)
ADDITIONAL_ARGS --js-app-bundle ${CMAKE_SOURCE_DIR}/samples/apps/logging/js
)

add_e2e_test(
NAME e2e_logging_http2
PYTHON_SCRIPT ${CMAKE_SOURCE_DIR}/tests/e2e_logging.py
BUCKET bucket_b
BUILD_DEPENDS logging logging_cose_only programmability js_generic
ADDITIONAL_ARGS
--js-app-bundle
${CMAKE_SOURCE_DIR}/samples/apps/logging/js
--http2
)

add_e2e_test(
NAME partitions
PYTHON_SCRIPT ${CMAKE_SOURCE_DIR}/tests/partitions_test.py
Expand Down
2 changes: 1 addition & 1 deletion doc/use_apps/issue_commands.rst
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ require setting ``ccf.gov.msg.type``, ``ccf.gov.msg.created_at``, and optionally

A signing script (``ccf_cose_sign1``) is provided as part of the `ccf Python package <https://pypi.org/project/ccf/>`_. The output can be piped directly into curl, or any other HTTP client.

Commands can also be signed using the `python-cwt <https://github.com/dajiaji/python-cwt/>`_ library, and sent with any standard HTTP library such as `Python HTTPX <https://www.python-httpx.org/>`_.
Commands can also be signed using the `python-cwt <https://github.com/dajiaji/python-cwt/>`_ library, and sent with any standard HTTP library such as `Python Requests <https://requests.readthedocs.io/>`_.

Idempotence
^^^^^^^^^^^
Expand Down
12 changes: 5 additions & 7 deletions scripts/fetch_amd_collateral.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from enum import Enum
import logging
import sys
import httpx
import requests
import base64
from cryptography import x509
from cryptography.hazmat.primitives import serialization
Expand Down Expand Up @@ -131,10 +131,8 @@ def make_chain_url(base_url, product_family):
)

logging.info(f"Fetching AMD leaf cert from {leaf_url}")
with httpx.Client() as client:
leaf_response = client.get(
leaf_url,
)
with requests.Session() as client:
leaf_response = client.get(leaf_url, timeout=30)
leaf_response.raise_for_status()
der = leaf_response.content
leaf = (
Expand All @@ -147,8 +145,8 @@ def make_chain_url(base_url, product_family):
chain_url = make_chain_url(args.base_url, args.product_family)

logging.info(f"Fetching AMD chain cert from {chain_url}")
with httpx.Client() as client:
chain_response = client.get(chain_url)
with requests.Session() as client:
chain_response = client.get(chain_url, timeout=30)
chain_response.raise_for_status()
chain = chain_response.text
logging.info(f"AMD chain cert response: {chain_response.text}")
Expand Down
2 changes: 1 addition & 1 deletion scripts/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
cryptography >= 48.0.1, < 49
httpx >= 0.28.1, < 0.29
requests >= 2.32.5, < 3
GitPython >= 3.1.45, < 4
1 change: 0 additions & 1 deletion tests/ci-buckets.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ bucket_b:
recovery_stale_snapshot_join_test
recovery_intermediate_snapshot_join_test
recovery_snapshot_endorsements_test
e2e_logging_http2
schema_test
nodes_test

Expand Down
42 changes: 29 additions & 13 deletions tests/client_protocols.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import subprocess

import infra.e2e_args
import infra.interfaces
import infra.net
import infra.network
import infra.proc
Expand All @@ -14,6 +15,10 @@
# As installed by setup scripts
H2SPEC_BIN = "/opt/h2spec/h2spec"

# HTTP/2 is only served on a dedicated interface: the Python test client
# only speaks HTTP/1.1, which the primary interface keeps for governance
HTTP2_RPC_INTERFACE = "http2_interface"


def compare_golden():
script_path = os.path.realpath(__file__)
Expand Down Expand Up @@ -114,27 +119,45 @@ def test_http2(network, args):
"--tls",
"--insecure",
"--host",
node.get_public_rpc_host(),
node.get_public_rpc_host(HTTP2_RPC_INTERFACE),
"--port",
f"{node.get_public_rpc_port()}",
f"{node.get_public_rpc_port(HTTP2_RPC_INTERFACE)}",
"--strict",
],
check=True,
)
assert r.returncode == 0


def single_interface_node(args):
# Retain only the primary interface, delete any others
nodes = infra.e2e_args.nodes(args, 1)
nodes[0].rpc_interfaces = {
infra.interfaces.PRIMARY_RPC_INTERFACE: nodes[0].get_primary_interface()
}
return nodes


def run(args):
# The TLS report is generated against a node with a single (HTTP/1.1)
# interface, and should still mention ALPN HTTP/1.1 as HTTP/2 is
# experimental as of 3.x
args.nodes = single_interface_node(args)
with infra.network.network(
args.nodes, args.binary_dir, args.debug_nodes, pdb=args.pdb
) as network:
network.start_and_open(args)
test_tls(network, args)

# Note: Start new network with HTTP/2 as TLS report should still
# mention ALPN HTTP/1.1 as HTTP/2 is experimental as of 3.x
args.http2 = True
args.nodes = infra.e2e_args.nodes(args, 1)
# Start a new network with an additional, dedicated HTTP/2 interface for
# the compliance test. The primary interface stays HTTP/1.1, so that the
# service can be opened with the Python client.
args.nodes = single_interface_node(args)
primary_interface = args.nodes[0].get_primary_interface()
args.nodes[0].rpc_interfaces[HTTP2_RPC_INTERFACE] = infra.interfaces.RPCInterface(
host=primary_interface.host,
app_protocol="HTTP2",
)
with infra.network.network(
args.nodes, args.binary_dir, args.debug_nodes, pdb=args.pdb
) as network:
Expand All @@ -146,11 +169,4 @@ def run(args):
args = infra.e2e_args.cli_args()
args.package = "samples/apps/logging/logging"

args.nodes = infra.e2e_args.nodes(args, 1)

# Retain only the primary interface, delete any others
args.nodes[0].rpc_interfaces = {
infra.interfaces.PRIMARY_RPC_INTERFACE: args.nodes[0].get_primary_interface()
}

run(args)
30 changes: 19 additions & 11 deletions tests/connections.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
import time

import fuzzing
import httpx
import infra.checker
import infra.e2e_args
import infra.interfaces
Expand Down Expand Up @@ -217,11 +216,6 @@ def create_connections_until_exhaustion(
client_fn(
identity="user0",
connection_timeout=1,
limits=httpx.Limits(
max_connections=1,
max_keepalive_connections=1,
keepalive_expiry=30,
),
)
)
)
Expand Down Expand Up @@ -269,10 +263,20 @@ def create_connections_until_exhaustion(
LOG.success(
f"{primary_pid} has {num_fds}/{max_fds} open file descriptors"
)
r = clients[0].get("/node/metrics")
assert r.status_code == http.HTTPStatus.OK, r.status_code
peak_metrics = r.body.json()["sessions"]
assert peak_metrics["active"] <= peak_metrics["peak"], peak_metrics
# Sessions refused with a 503 are closed asynchronously by the
# node, so briefly wait for the active count to settle
end_time = time.time() + 3
while True:
r = clients[0].get("/node/metrics")
assert r.status_code == http.HTTPStatus.OK, r.status_code
peak_metrics = r.body.json()["sessions"]
assert peak_metrics["active"] <= peak_metrics["peak"], peak_metrics
if (
peak_metrics["active"] == len(healthy_clients)
or time.time() > end_time
):
break
time.sleep(0.1)
assert peak_metrics["active"] == len(healthy_clients), (
peak_metrics,
len(healthy_clients),
Expand Down Expand Up @@ -352,14 +356,18 @@ def create_connections_until_exhaustion(
resource.prlimit(primary_pid, resource.RLIMIT_NOFILE, (max_fds, max_fds))
LOG.success(f"Setting max fds to dangerously low {max_fds} on {primary_pid}")

# The node is expected to crash when it runs out of file descriptors.
# That may only happen after the client has seen its responses, as
# ledger writes are asynchronous, so tolerate fatal errors at shutdown
# regardless of whether the crash is observed by the client.
network.ignore_errors_on_shutdown()
try:
num_fds = create_connections_until_exhaustion(to_create)
except Exception as e:
LOG.warning(
f"Node with only {max_fds} fds crashed when allowed to created {args.max_open_sessions} sessions, as expected"
)
LOG.warning(e)
network.ignore_errors_on_shutdown()
else:
LOG.warning("Expected a fatal crash and saw none!")

Expand Down
63 changes: 29 additions & 34 deletions tests/e2e_common_endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,16 +248,12 @@ def run_large_message_test(
expected_errors[metrics_name] += 1
assert get_main_interface_errors() == expected_errors

def get_sizes(n, http2):
ns = [n // 2, n - 10, n - 1, n, n + 1, n + 10, n * 2]
if not http2:
# nghttp2 does not currently allow header larger than 64KB
# https://github.com/nghttp2/nghttp2/issues/1841
ns.append(n * 20)
def get_sizes(n):
ns = [n // 2, n - 10, n - 1, n, n + 1, n + 10, n * 2, n * 20]
random.shuffle(ns)
return ns

for s in get_sizes(args.max_http_body_size, args.http2):
for s in get_sizes(args.max_http_body_size):
long_msg = "X" * s
LOG.info(f"Verifying cap on max body size, sending a {s} byte body")
run_large_message_test(
Expand All @@ -270,7 +266,7 @@ def get_sizes(n, http2):
headers={"content-type": "application/json"},
)

for s in get_sizes(args.max_http_header_size, args.http2):
for s in get_sizes(args.max_http_header_size):
long_header = "X" * s
LOG.info(f"Verifying cap on max header value, sending a {s} byte header value")
run_large_message_test(
Expand All @@ -292,36 +288,35 @@ def get_sizes(n, http2):
headers={long_header: "some header value"},
)

if not args.http2:
for size in (
args.max_http_request_target_size - 1,
args.max_http_request_target_size,
args.max_http_request_target_size + 1,
):
prefix = "/node/commit?padding="
if size < len(prefix):
LOG.warning(
f"Skipping {size} byte request target: the test endpoint "
f"requires at least {len(prefix)} bytes"
)
continue
target = prefix + "a" * (size - len(prefix))
assert len(target) == size
LOG.info(f"Verifying cap on request target, sending a {size} byte target")
run_large_message_test(
args.max_http_request_target_size,
http.HTTPStatus.REQUEST_URI_TOO_LONG,
"RequestTargetTooLong",
"request_target_too_long",
len(target),
path=target,
for size in (
args.max_http_request_target_size - 1,
args.max_http_request_target_size,
args.max_http_request_target_size + 1,
):
prefix = "/node/commit?padding="
if size < len(prefix):
LOG.warning(
f"Skipping {size} byte request target: the test endpoint "
f"requires at least {len(prefix)} bytes"
)
continue
target = prefix + "a" * (size - len(prefix))
assert len(target) == size
LOG.info(f"Verifying cap on request target, sending a {size} byte target")
run_large_message_test(
args.max_http_request_target_size,
http.HTTPStatus.REQUEST_URI_TOO_LONG,
"RequestTargetTooLong",
"request_target_too_long",
len(target),
path=target,
)

# Note: infra generally inserts extra headers (eg, content type and length, user-agent, accept)
extra_headers_count = infra.clients.CCFClient.default_impl_type.extra_headers_count(
args.http2
extra_headers_count = (
infra.clients.CCFClient.default_impl_type.extra_headers_count()
)
for s in get_sizes(args.max_http_headers_count, args.http2):
for s in get_sizes(args.max_http_headers_count):
LOG.info(f"Verifying on cap on max headers count, sending {s} headers")
headers = {f"header-{h}": str(h) for h in range(s - extra_headers_count)}
run_large_message_test(
Expand Down
Loading
Loading