Skip to content

Commit 13fd504

Browse files
jiridanekopenshift-merge-bot[bot]
authored andcommitted
RHOAIENG-18459: chore(tests/containers/workbenches): make the ipv6 listening test work on macOS
1 parent 216dc0a commit 13fd504

File tree

3 files changed

+114
-7
lines changed

3 files changed

+114
-7
lines changed
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import json
2+
import logging
3+
import socket
4+
import subprocess
5+
from tests.containers.schemas import PodmanMachineInspect
6+
7+
logging.basicConfig(level=logging.DEBUG)
8+
9+
def open_ssh_tunnel(machine_name: str, local_port: int, remote_port: int, remote_interface: str = "localhost") -> subprocess.Popen:
10+
# Load and parse the Podman machine data
11+
json_data = subprocess.check_output(["podman", "machine", "inspect", machine_name], text=True)
12+
inspect = PodmanMachineInspect(machines=json.loads(json_data))
13+
machines = inspect.machines
14+
15+
machine = next((m for m in machines if m.Name == machine_name), None)
16+
if not machine:
17+
raise ValueError(f"Machine '{machine_name}' not found")
18+
19+
ssh_command = [
20+
"ssh",
21+
"-i", machine.SSHConfig.IdentityPath,
22+
"-p", str(machine.SSHConfig.Port),
23+
"-L", f"{local_port}:{remote_interface}:{remote_port}",
24+
"-N", # Do not execute a remote command
25+
"-o", "UserKnownHostsFile=/dev/null",
26+
"-o", "StrictHostKeyChecking=no",
27+
f"{machine.SSHConfig.RemoteUsername}@localhost"
28+
]
29+
30+
# Open the SSH tunnel
31+
process = subprocess.Popen(ssh_command)
32+
33+
logging.info(f"SSH tunnel opened for {machine_name}: {remote_interface}:{local_port} -> localhost:{remote_port}")
34+
return process
35+
36+
def find_free_port() -> int:
37+
"""Find a free port on the local machine.
38+
:return: A port number that is currently free and available for use.
39+
"""
40+
with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as s:
41+
s.bind(('', 0)) # Bind to a free port provided by the system
42+
s.listen(1)
43+
port = s.getsockname()[1]
44+
return port
45+
46+
# Usage example
47+
if __name__ == "__main__":
48+
tunnel_process = open_ssh_tunnel(
49+
"podman-machine-default",
50+
8080, 8080,
51+
remote_interface="[fc00::2]"
52+
)
53+
54+
# Keep the tunnel open until user interrupts
55+
try:
56+
tunnel_process.wait()
57+
except KeyboardInterrupt:
58+
tunnel_process.terminate()
59+
print("SSH tunnel closed")

tests/containers/schemas.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
from pydantic import BaseModel
2+
from typing import Optional
3+
from datetime import datetime
4+
5+
class ConfigDir(BaseModel):
6+
Path: str
7+
8+
class PodmanSocket(BaseModel):
9+
Path: str
10+
11+
class ConnectionInfo(BaseModel):
12+
PodmanSocket: PodmanSocket
13+
PodmanPipe: Optional[str] = None
14+
15+
class Resources(BaseModel):
16+
CPUs: int
17+
DiskSize: int
18+
Memory: int
19+
USBs: list[str] = []
20+
21+
class SSHConfig(BaseModel):
22+
IdentityPath: str
23+
Port: int
24+
RemoteUsername: str
25+
26+
class PodmanMachine(BaseModel):
27+
ConfigDir: ConfigDir
28+
ConnectionInfo: ConnectionInfo
29+
Created: datetime
30+
LastUp: datetime
31+
Name: str
32+
Resources: Resources
33+
SSHConfig: SSHConfig
34+
State: str
35+
UserModeNetworking: bool
36+
Rootful: bool
37+
Rosetta: bool
38+
39+
# generated from `podman machine inspect` output by smart tooling
40+
class PodmanMachineInspect(BaseModel):
41+
machines: list[PodmanMachine]

tests/containers/workbenches/workbench_image_test.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
import pytest
2020

21-
from tests.containers import docker_utils
21+
from tests.containers import docker_utils, podman_machine_utils
2222

2323

2424
class TestWorkbenchImage:
@@ -53,10 +53,6 @@ def test_ipv6_only(self, image: str, test_frame):
5353
Workarounds for macOS will be needed, so that's why it's a separate test."""
5454
skip_if_not_workbench_image(image)
5555

56-
if platform.system().lower() == 'darwin':
57-
pytest.skip("Podman on macOS does not support exposing IPv6 ports,"
58-
" see https://github.com/containers/podman/issues/15140")
59-
6056
# network is made ipv6 by only defining the ipv6 subnet for it
6157
# do _not_ set the ipv6=true option, that would actually make it dual-stack
6258
# https://github.com/containers/podman/issues/22359#issuecomment-2196817604
@@ -87,8 +83,19 @@ def test_ipv6_only(self, image: str, test_frame):
8783
container.get_wrapped_container().reload()
8884
ipv6_address = (container.get_wrapped_container().attrs
8985
["NetworkSettings"]["Networks"][network.name]["GlobalIPv6Address"])
90-
91-
container._connect(container_host=ipv6_address, container_port=container.port)
86+
if platform.system().lower() == 'darwin':
87+
# the container host is a podman machine, we need to expose port on podman machine first
88+
host = "localhost"
89+
port = podman_machine_utils.find_free_port()
90+
process = podman_machine_utils.open_ssh_tunnel("podman-machine-default",
91+
local_port=port, remote_port=container.port,
92+
remote_interface=f"[{ipv6_address}]")
93+
test_frame.append(process, lambda p: p.kill())
94+
else:
95+
host = ipv6_address
96+
port = container.port
97+
98+
container._connect(container_host=host, container_port=port)
9299
finally:
93100
# try to grab logs regardless of whether container started or not
94101
stdout, stderr = container.get_logs()

0 commit comments

Comments
 (0)