Merge branch 'fix/ota_resumption_ci_failures' into 'master'

fix: solve OTA resumption CI failures

Closes IDFCI-3505 and IDFCI-3328

See merge request espressif/esp-idf!45984
This commit is contained in:
Aditya Patwardhan
2026-03-30 14:44:14 +05:30
@@ -4,6 +4,7 @@ import http.server
import multiprocessing
import os
import random
import socket
import ssl
import struct
import subprocess
@@ -40,12 +41,26 @@ def restart_device_with_random_delay(dut: Dut, min_delay: int = 10, max_delay: i
print('Device restarted after random delay.')
def https_request_handler() -> Callable[..., http.server.BaseHTTPRequestHandler]:
def https_request_handler(
ssl_context: ssl.SSLContext | None = None,
) -> Callable[..., http.server.BaseHTTPRequestHandler]:
"""
Returns a request handler class that handles broken pipe exception
Returns a request handler class that handles broken pipe exception.
If ssl_context is provided, SSL wrapping is done per-connection in the handler
thread (inside setup()) so that the server's main accept loop never blocks on
an SSL handshake — critical for OTA resumption tests where the device hard-resets
mid-connection.
"""
class RequestHandler(RangeRequestHandler):
def setup(self) -> None:
if ssl_context is not None:
sock: socket.socket = self.request # type: ignore[has-type]
sock.settimeout(10) # Timeout for SSL handshake
self.request = ssl_context.wrap_socket(sock, server_side=True)
self.request.settimeout(None) # Back to blocking mode for data transfer
super().setup()
def finish(self) -> None:
try:
if not self.wfile.closed:
@@ -66,13 +81,15 @@ def https_request_handler() -> Callable[..., http.server.BaseHTTPRequestHandler]
def start_https_server(ota_image_dir: str, server_ip: str, server_port: int) -> None:
os.chdir(ota_image_dir)
requestHandler = https_request_handler()
httpd = http.server.HTTPServer((server_ip, server_port), requestHandler)
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
ssl_context.load_cert_chain(certfile=server_file, keyfile=key_file)
httpd.socket = ssl_context.wrap_socket(httpd.socket, server_side=True)
# Pass ssl_context to handler — SSL is done per-connection in handler threads,
# keeping the main accept loop on plain TCP (never blocks on SSL handshake)
requestHandler = https_request_handler(ssl_context)
httpd = http.server.ThreadingHTTPServer((server_ip, server_port), requestHandler)
httpd.daemon_threads = True # Handler threads won't block server shutdown
httpd.serve_forever()
@@ -94,12 +111,26 @@ def start_chunked_server(ota_image_dir: str, server_port: int) -> subprocess.Pop
return chunked_server
def redirect_handler_factory(url: str) -> Callable[..., http.server.BaseHTTPRequestHandler]:
def redirect_handler_factory(
url: str,
ssl_context: ssl.SSLContext | None = None,
) -> Callable[..., http.server.BaseHTTPRequestHandler]:
"""
Returns a request handler class that redirects to supplied `url`
Returns a request handler class that redirects to supplied `url`.
If ssl_context is provided, SSL wrapping is done per-connection in the handler
thread (inside setup()) so that the server's main accept loop never blocks on
an SSL handshake.
"""
class RedirectHandler(http.server.SimpleHTTPRequestHandler):
def setup(self) -> None:
if ssl_context is not None:
sock: socket.socket = self.request # type: ignore[has-type]
sock.settimeout(10) # Timeout for SSL handshake
self.request = ssl_context.wrap_socket(sock, server_side=True)
self.request.settimeout(None) # Back to blocking mode for data transfer
super().setup()
def do_GET(self) -> None:
print('Sending resp, URL: ' + url)
self.send_response(301)
@@ -117,16 +148,18 @@ def redirect_handler_factory(url: str) -> Callable[..., http.server.BaseHTTPRequ
def start_redirect_server(ota_image_dir: str, server_ip: str, server_port: int, redirection_port: int) -> None:
os.chdir(ota_image_dir)
redirectHandler = redirect_handler_factory(
'https://' + server_ip + ':' + str(redirection_port) + '/advanced_https_ota.bin'
)
httpd = http.server.HTTPServer((server_ip, server_port), redirectHandler)
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
ssl_context.load_cert_chain(certfile=server_file, keyfile=key_file)
httpd.socket = ssl_context.wrap_socket(httpd.socket, server_side=True)
# Pass ssl_context to handler — SSL is done per-connection in handler threads,
# keeping the main accept loop on plain TCP (never blocks on SSL handshake)
redirectHandler = redirect_handler_factory(
'https://' + server_ip + ':' + str(redirection_port) + '/advanced_https_ota.bin',
ssl_context,
)
httpd = http.server.ThreadingHTTPServer((server_ip, server_port), redirectHandler)
httpd.daemon_threads = True # Handler threads won't block server shutdown
httpd.serve_forever()