mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-11-05 06:41:13 +02:00
refactor: ruff autofixes
This commit is contained in:
455 files changed
+12322
-14762
No files matched your search
@@ -83,7 +83,7 @@ class DockerHTTPClient:
|
||||
self._connected = True
|
||||
|
||||
# Parse API version
|
||||
api_version = parse_version(docker_info['ApiVersion'])
|
||||
api_version = parse_version(docker_info["ApiVersion"])
|
||||
docker_version = docker_info["Version"]
|
||||
|
||||
logger.info(f"Connected to Docker {docker_version}, API {api_version}")
|
||||
@@ -102,7 +102,7 @@ class DockerHTTPClient:
|
||||
logger.info(f"Using Docker API version {self._api_version}")
|
||||
else:
|
||||
# Use Docker daemon's actual API version
|
||||
self._api_version = docker_info['ApiVersion']
|
||||
self._api_version = docker_info["ApiVersion"]
|
||||
logger.info(f"Using Docker API version {self._api_version} (daemon native)")
|
||||
|
||||
except (aiohttp.ClientError, FileNotFoundError) as e:
|
||||
@@ -124,7 +124,7 @@ class DockerHTTPClient:
|
||||
Response JSON data
|
||||
"""
|
||||
# Check connection and version on first request
|
||||
check_connection = kwargs.pop('check_connection', True)
|
||||
check_connection = kwargs.pop("check_connection", True)
|
||||
if check_connection and not self._connected:
|
||||
await self._check_connection()
|
||||
|
||||
@@ -147,22 +147,21 @@ class DockerHTTPClient:
|
||||
except RuntimeError as e:
|
||||
# Retry with lower API version if Docker daemon doesn't support current version
|
||||
error_msg = str(e)
|
||||
if ("400" in error_msg or "not found" in error_msg.lower()) and self._api_version == DOCKER_PREFERRED_API_VERSION:
|
||||
logger.warning(f"Docker daemon doesn't support API version {self._api_version}, falling back to {DOCKER_MINIMUM_API_VERSION}")
|
||||
if (
|
||||
"400" in error_msg or "not found" in error_msg.lower()
|
||||
) and self._api_version == DOCKER_PREFERRED_API_VERSION:
|
||||
logger.warning(
|
||||
f"Docker daemon doesn't support API version {self._api_version}, falling back to {DOCKER_MINIMUM_API_VERSION}"
|
||||
)
|
||||
self._api_version = DOCKER_MINIMUM_API_VERSION
|
||||
return await self._request(method, endpoint, check_connection=False, **kwargs)
|
||||
raise
|
||||
|
||||
async def create_network(self, name: str, driver: str = "bridge", subnet: str = None):
|
||||
"""Create Docker network."""
|
||||
data = {
|
||||
"Name": name,
|
||||
"Driver": driver
|
||||
}
|
||||
data = {"Name": name, "Driver": driver}
|
||||
if subnet:
|
||||
data["IPAM"] = {
|
||||
"Config": [{"Subnet": subnet}]
|
||||
}
|
||||
data["IPAM"] = {"Config": [{"Subnet": subnet}]}
|
||||
await self._request("POST", "networks/create", json=data)
|
||||
|
||||
async def get_network(self, name: str):
|
||||
@@ -176,19 +175,12 @@ class DockerHTTPClient:
|
||||
|
||||
async def create_container(self, name: str, image: str, **kwargs):
|
||||
"""Create container."""
|
||||
data = {
|
||||
"Image": image,
|
||||
"name": name,
|
||||
"HostConfig": {},
|
||||
"NetworkingConfig": {}
|
||||
}
|
||||
data = {"Image": image, "name": name, "HostConfig": {}, "NetworkingConfig": {}}
|
||||
|
||||
# Handle network config
|
||||
if "network" in kwargs:
|
||||
network_name = kwargs.pop("network")
|
||||
data["NetworkingConfig"]["EndpointsConfig"] = {
|
||||
network_name: {}
|
||||
}
|
||||
data["NetworkingConfig"]["EndpointsConfig"] = {network_name: {}}
|
||||
|
||||
# Handle environment variables
|
||||
if "environment" in kwargs:
|
||||
|
||||
@@ -92,10 +92,7 @@ logger = logging.getLogger(__name__)
|
||||
def setup_logging(verbose: bool = False):
|
||||
"""Setup logging configuration."""
|
||||
level = logging.DEBUG if verbose else logging.INFO
|
||||
logging.basicConfig(
|
||||
level=level,
|
||||
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
||||
)
|
||||
logging.basicConfig(level=level, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
|
||||
|
||||
|
||||
async def cmd_start(args) -> int:
|
||||
@@ -119,7 +116,7 @@ async def cmd_start(args) -> int:
|
||||
image=args.image,
|
||||
memory=args.memory,
|
||||
cpus=args.cpus,
|
||||
pids_limit=args.pids_limit
|
||||
pids_limit=args.pids_limit,
|
||||
)
|
||||
|
||||
print(json.dumps(result, indent=2))
|
||||
@@ -143,10 +140,7 @@ async def cmd_stop(args) -> int:
|
||||
"""
|
||||
manager = WebWiresharkManager()
|
||||
try:
|
||||
await manager.stop_wireshark_session(
|
||||
project_id=args.project_id,
|
||||
link_id=args.link_id
|
||||
)
|
||||
await manager.stop_wireshark_session(project_id=args.project_id, link_id=args.link_id)
|
||||
print(json.dumps({"status": "stopped"}))
|
||||
return 0
|
||||
except Exception as e:
|
||||
@@ -172,7 +166,7 @@ async def cmd_restart(args) -> int:
|
||||
project_id=args.project_id,
|
||||
link_id=args.link_id,
|
||||
jwt_token=args.jwt_token,
|
||||
capture_stream_url=args.capture_url
|
||||
capture_stream_url=args.capture_url,
|
||||
)
|
||||
print(json.dumps(result, indent=2))
|
||||
return 0
|
||||
@@ -274,14 +268,8 @@ async def cmd_delete(args) -> int:
|
||||
|
||||
def create_parser() -> argparse.ArgumentParser:
|
||||
"""Create command line argument parser."""
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Web Wireshark container management"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--verbose", "-v",
|
||||
action="store_true",
|
||||
help="Enable verbose logging"
|
||||
)
|
||||
parser = argparse.ArgumentParser(description="Web Wireshark container management")
|
||||
parser.add_argument("--verbose", "-v", action="store_true", help="Enable verbose logging")
|
||||
|
||||
subparsers = parser.add_subparsers(dest="command", help="Commands")
|
||||
|
||||
@@ -290,32 +278,13 @@ def create_parser() -> argparse.ArgumentParser:
|
||||
start_parser.add_argument("--project-id", required=True, type=validate_uuid, help="Project ID")
|
||||
start_parser.add_argument("--link-id", required=True, type=validate_uuid, help="Link ID")
|
||||
start_parser.add_argument("--jwt-token", required=True, help="JWT token")
|
||||
start_parser.add_argument("--capture-url", help="Capture stream URL (auto-detected if not provided)")
|
||||
start_parser.add_argument(
|
||||
"--capture-url",
|
||||
help="Capture stream URL (auto-detected if not provided)"
|
||||
)
|
||||
start_parser.add_argument(
|
||||
"--image",
|
||||
default="gns3/web-wireshark:latest",
|
||||
help="Docker image (default: gns3/web-wireshark:latest)"
|
||||
)
|
||||
start_parser.add_argument(
|
||||
"--memory",
|
||||
default="2g",
|
||||
help="Memory limit (default: 2g)"
|
||||
)
|
||||
start_parser.add_argument(
|
||||
"--cpus",
|
||||
type=float,
|
||||
default=1.0,
|
||||
help="CPU cores (default: 1.0)"
|
||||
)
|
||||
start_parser.add_argument(
|
||||
"--pids-limit",
|
||||
type=int,
|
||||
default=1000,
|
||||
help="Process limit (default: 1000)"
|
||||
"--image", default="gns3/web-wireshark:latest", help="Docker image (default: gns3/web-wireshark:latest)"
|
||||
)
|
||||
start_parser.add_argument("--memory", default="2g", help="Memory limit (default: 2g)")
|
||||
start_parser.add_argument("--cpus", type=float, default=1.0, help="CPU cores (default: 1.0)")
|
||||
start_parser.add_argument("--pids-limit", type=int, default=1000, help="Process limit (default: 1000)")
|
||||
|
||||
# Stop command
|
||||
stop_parser = subparsers.add_parser("stop", help="Stop Web Wireshark session")
|
||||
@@ -327,10 +296,7 @@ def create_parser() -> argparse.ArgumentParser:
|
||||
restart_parser.add_argument("--project-id", required=True, type=validate_uuid, help="Project ID")
|
||||
restart_parser.add_argument("--link-id", required=True, type=validate_uuid, help="Link ID")
|
||||
restart_parser.add_argument("--jwt-token", required=True, help="JWT token")
|
||||
restart_parser.add_argument(
|
||||
"--capture-url",
|
||||
help="Capture stream URL (auto-detected if not provided)"
|
||||
)
|
||||
restart_parser.add_argument("--capture-url", help="Capture stream URL (auto-detected if not provided)")
|
||||
|
||||
# Stop all command
|
||||
stop_all_parser = subparsers.add_parser("stop-all", help="Stop all sessions")
|
||||
|
||||
@@ -63,15 +63,16 @@ class WebWiresharkManager:
|
||||
"""
|
||||
try:
|
||||
proc = await asyncio.create_subprocess_exec(
|
||||
"docker", "exec", container_id,
|
||||
"bash", "-c", "echo 'ping'",
|
||||
"docker",
|
||||
"exec",
|
||||
container_id,
|
||||
"bash",
|
||||
"-c",
|
||||
"echo 'ping'",
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE
|
||||
)
|
||||
stdout, _ = await asyncio.wait_for(
|
||||
proc.communicate(),
|
||||
timeout=self.CONTAINER_EXEC_TIMEOUT
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
)
|
||||
stdout, _ = await asyncio.wait_for(proc.communicate(), timeout=self.CONTAINER_EXEC_TIMEOUT)
|
||||
return proc.returncode == 0 and b"ping" in stdout
|
||||
except asyncio.TimeoutError:
|
||||
logger.warning(f"Container {container_id[:12]} health check timeout")
|
||||
@@ -99,16 +100,17 @@ class WebWiresharkManager:
|
||||
|
||||
try:
|
||||
proc = await asyncio.create_subprocess_exec(
|
||||
"docker", "exec", container_id,
|
||||
"bash", "-c", command,
|
||||
"docker",
|
||||
"exec",
|
||||
container_id,
|
||||
"bash",
|
||||
"-c",
|
||||
command,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
)
|
||||
try:
|
||||
stdout, stderr = await asyncio.wait_for(
|
||||
proc.communicate(),
|
||||
timeout=timeout
|
||||
)
|
||||
stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=timeout)
|
||||
return (proc.returncode, stdout.decode().strip(), stderr.decode().strip())
|
||||
except asyncio.TimeoutError:
|
||||
logger.error(f"Command timeout after {timeout}s: {command}")
|
||||
@@ -142,7 +144,7 @@ class WebWiresharkManager:
|
||||
logger.debug(f"No processes found matching pattern '{pattern}'")
|
||||
return
|
||||
|
||||
pids = pids_str.replace('\n', ' ')
|
||||
pids = pids_str.replace("\n", " ")
|
||||
logger.info(f"Found matching processes for '{pattern}': PIDs={pids}")
|
||||
|
||||
# Kill the processes using their container-local PIDs
|
||||
@@ -170,10 +172,13 @@ class WebWiresharkManager:
|
||||
try:
|
||||
# Get container init PID from host perspective
|
||||
proc = await asyncio.create_subprocess_exec(
|
||||
"docker", "inspect", container_id,
|
||||
"--format", "{{.State.Pid}}",
|
||||
"docker",
|
||||
"inspect",
|
||||
container_id,
|
||||
"--format",
|
||||
"{{.State.Pid}}",
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
)
|
||||
stdout, _ = await proc.communicate()
|
||||
container_init_pid = stdout.decode().strip()
|
||||
@@ -187,15 +192,13 @@ class WebWiresharkManager:
|
||||
|
||||
# Check for residual processes from host perspective
|
||||
proc = await asyncio.create_subprocess_exec(
|
||||
"ps", "-eo", "pid,ppid,args",
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE
|
||||
"ps", "-eo", "pid,ppid,args", stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
|
||||
)
|
||||
stdout, _ = await proc.communicate()
|
||||
|
||||
# Build parent->children mapping
|
||||
children_map = {}
|
||||
for line in stdout.decode().strip().split('\n'):
|
||||
for line in stdout.decode().strip().split("\n"):
|
||||
parts = line.split(None, 2)
|
||||
if len(parts) < 3:
|
||||
continue
|
||||
@@ -218,14 +221,15 @@ class WebWiresharkManager:
|
||||
|
||||
# Check if any descendant processes match display patterns
|
||||
import re
|
||||
|
||||
patterns = [
|
||||
f'xpra.*:{display}',
|
||||
f'Xvfb.*:{display}',
|
||||
f'wireshark.*:{display}',
|
||||
f'pulseaudio.*display=:{display}'
|
||||
f"xpra.*:{display}",
|
||||
f"Xvfb.*:{display}",
|
||||
f"wireshark.*:{display}",
|
||||
f"pulseaudio.*display=:{display}",
|
||||
]
|
||||
|
||||
for line in stdout.decode().strip().split('\n'):
|
||||
for line in stdout.decode().strip().split("\n"):
|
||||
parts = line.split(None, 2)
|
||||
if len(parts) < 3:
|
||||
continue
|
||||
@@ -248,13 +252,11 @@ class WebWiresharkManager:
|
||||
# /proc/<pid>/root points to container filesystem
|
||||
lock_paths = [
|
||||
f"/proc/{container_init_pid}/root/tmp/.X{display}-lock",
|
||||
f"/proc/{container_init_pid}/root/tmp/.X11-unix/X{display}"
|
||||
f"/proc/{container_init_pid}/root/tmp/.X11-unix/X{display}",
|
||||
]
|
||||
for lock_path in lock_paths:
|
||||
proc = await asyncio.create_subprocess_exec(
|
||||
"test", "-e", lock_path,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE
|
||||
"test", "-e", lock_path, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
|
||||
)
|
||||
await proc.communicate()
|
||||
if proc.returncode == 0:
|
||||
@@ -264,9 +266,7 @@ class WebWiresharkManager:
|
||||
# Check for xpra socket files
|
||||
socket_path = f"/proc/{container_init_pid}/root/run/user/1000/xpra/{display}/socket"
|
||||
proc = await asyncio.create_subprocess_exec(
|
||||
"test", "-e", socket_path,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE
|
||||
"test", "-e", socket_path, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
|
||||
)
|
||||
await proc.communicate()
|
||||
if proc.returncode == 0:
|
||||
@@ -298,10 +298,13 @@ class WebWiresharkManager:
|
||||
try:
|
||||
# Get container init PID from host perspective
|
||||
proc = await asyncio.create_subprocess_exec(
|
||||
"docker", "inspect", container_id,
|
||||
"--format", "{{.State.Pid}}",
|
||||
"docker",
|
||||
"inspect",
|
||||
container_id,
|
||||
"--format",
|
||||
"{{.State.Pid}}",
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
)
|
||||
stdout, _ = await proc.communicate()
|
||||
container_init_pid = stdout.decode().strip()
|
||||
@@ -314,9 +317,7 @@ class WebWiresharkManager:
|
||||
# List all processes from host perspective with PID, PPID, and command
|
||||
# This is faster than docker exec and allows us to walk the process tree
|
||||
proc = await asyncio.create_subprocess_exec(
|
||||
"ps", "-eo", "pid,ppid,args",
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE
|
||||
"ps", "-eo", "pid,ppid,args", stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
|
||||
)
|
||||
stdout, _ = await proc.communicate()
|
||||
|
||||
@@ -328,7 +329,7 @@ class WebWiresharkManager:
|
||||
children_map = {} # ppid -> [pid]
|
||||
process_info = {} # pid -> (ppid, command)
|
||||
|
||||
for line in stdout.decode().strip().split('\n'):
|
||||
for line in stdout.decode().strip().split("\n"):
|
||||
parts = line.split(None, 2)
|
||||
if len(parts) < 3:
|
||||
continue
|
||||
@@ -371,9 +372,7 @@ class WebWiresharkManager:
|
||||
logger.info(f"Killing {len(matching_pids)} processes: {matching_pids}")
|
||||
# Kill all matching PIDs from host perspective
|
||||
proc = await asyncio.create_subprocess_exec(
|
||||
"kill", "-9", *matching_pids,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE
|
||||
"kill", "-9", *matching_pids, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
|
||||
)
|
||||
await proc.communicate()
|
||||
logger.debug("Kill completed from host perspective")
|
||||
@@ -396,16 +395,12 @@ class WebWiresharkManager:
|
||||
"""
|
||||
try:
|
||||
# Combine all patterns into a single regex using OR operator
|
||||
combined_pattern = '|'.join(f'({pattern})' for pattern in patterns)
|
||||
combined_pattern = "|".join(f"({pattern})" for pattern in patterns)
|
||||
|
||||
# Single pgrep to find all matching processes
|
||||
pgrep_cmd = f'pids=$(pgrep -f "{combined_pattern}" 2>/dev/null || true); if [ -n "$pids" ]; then echo "Found processes: $pids"; kill -9 $pids 2>/dev/null || true; fi'
|
||||
|
||||
returncode, stdout, stderr = await self._exec_in_container(
|
||||
container_id,
|
||||
pgrep_cmd,
|
||||
timeout=5
|
||||
)
|
||||
returncode, stdout, stderr = await self._exec_in_container(container_id, pgrep_cmd, timeout=5)
|
||||
|
||||
if "Found processes:" in stdout:
|
||||
logger.info(stdout.strip())
|
||||
@@ -422,10 +417,12 @@ class WebWiresharkManager:
|
||||
"""
|
||||
# Clean up X lock files and xpra socket files in a single docker exec
|
||||
# This reduces docker exec calls from 2 to 1, improving stop performance
|
||||
cmd = (f"rm -f /tmp/.X{display}-lock /tmp/.X11-unix/X{display} "
|
||||
f"/run/user/1000/xpra/{display}/socket "
|
||||
f"/run/user/1000/xpra/*-{display} "
|
||||
f"/home/gns3/.xpra/*-{display} 2>/dev/null || true")
|
||||
cmd = (
|
||||
f"rm -f /tmp/.X{display}-lock /tmp/.X11-unix/X{display} "
|
||||
f"/run/user/1000/xpra/{display}/socket "
|
||||
f"/run/user/1000/xpra/*-{display} "
|
||||
f"/home/gns3/.xpra/*-{display} 2>/dev/null || true"
|
||||
)
|
||||
returncode, stdout, stderr = await self._exec_in_container(container_id, cmd)
|
||||
logger.debug(f"Cleanup X locks and xpra sockets: returncode={returncode}")
|
||||
|
||||
@@ -436,9 +433,11 @@ class WebWiresharkManager:
|
||||
container_id: Container ID
|
||||
display: Display number (e.g., 10210)
|
||||
"""
|
||||
cmd = (f"rm -f /run/user/1000/xpra/{display}/socket "
|
||||
f"/run/user/1000/xpra/*-{display} "
|
||||
f"/home/gns3/.xpra/*-{display} 2>/dev/null || true")
|
||||
cmd = (
|
||||
f"rm -f /run/user/1000/xpra/{display}/socket "
|
||||
f"/run/user/1000/xpra/*-{display} "
|
||||
f"/home/gns3/.xpra/*-{display} 2>/dev/null || true"
|
||||
)
|
||||
returncode, stdout, stderr = await self._exec_in_container(container_id, cmd)
|
||||
logger.debug(f"Cleanup socket files: returncode={returncode}")
|
||||
|
||||
@@ -457,20 +456,15 @@ class WebWiresharkManager:
|
||||
return 0
|
||||
|
||||
# Extract number and unit
|
||||
match = re.match(r'(\d+(?:\.\d+)?)\s*([kmg]b?)?', memory_str)
|
||||
match = re.match(r"(\d+(?:\.\d+)?)\s*([kmg]b?)?", memory_str)
|
||||
if not match:
|
||||
raise ValueError(f"Invalid memory format: {memory_str}")
|
||||
|
||||
value = float(match.group(1))
|
||||
unit = match.group(2) or 'b'
|
||||
unit = match.group(2) or "b"
|
||||
|
||||
# Convert to bytes
|
||||
multipliers = {
|
||||
'b': 1,
|
||||
'k': 1024,
|
||||
'm': 1024 * 1024,
|
||||
'g': 1024 * 1024 * 1024
|
||||
}
|
||||
multipliers = {"b": 1, "k": 1024, "m": 1024 * 1024, "g": 1024 * 1024 * 1024}
|
||||
|
||||
unit = unit[0] # Take first character
|
||||
return int(value * multipliers.get(unit, 1))
|
||||
@@ -511,8 +505,7 @@ class WebWiresharkManager:
|
||||
if container_id:
|
||||
try:
|
||||
returncode, stdout, stderr = await self._exec_in_container(
|
||||
container_id,
|
||||
"cat /proc/net/route | grep -E '^eth0\\s+00000000' | awk '{print $3}' | head -1"
|
||||
container_id, "cat /proc/net/route | grep -E '^eth0\\s+00000000' | awk '{print $3}' | head -1"
|
||||
)
|
||||
logger.info(f"Gateway detection - fallback method: returncode={returncode}, stdout='{stdout}'")
|
||||
if returncode == 0 and stdout.strip():
|
||||
@@ -557,8 +550,7 @@ class WebWiresharkManager:
|
||||
# Use 'hostname -I' to get all IP addresses and take the first one
|
||||
# This works on most Linux systems and is more portable than 'ip' command
|
||||
returncode, stdout, stderr = await self._exec_in_container(
|
||||
container_id,
|
||||
"hostname -I 2>/dev/null | awk '{print $1}'"
|
||||
container_id, "hostname -I 2>/dev/null | awk '{print $1}'"
|
||||
)
|
||||
if returncode == 0 and stdout.strip():
|
||||
container_ip = stdout.strip()
|
||||
@@ -585,7 +577,7 @@ class WebWiresharkManager:
|
||||
"""
|
||||
try:
|
||||
parsed = urlparse(url)
|
||||
if parsed.hostname in ('localhost', '127.0.0.1', '0.0.0.0'):
|
||||
if parsed.hostname in ("localhost", "127.0.0.1", "0.0.0.0"):
|
||||
gateway = await self._get_container_gateway_ip(container_id)
|
||||
if gateway:
|
||||
fixed_url = f"{parsed.scheme}://{gateway}:{parsed.port or 3080}{parsed.path}"
|
||||
@@ -627,11 +619,7 @@ class WebWiresharkManager:
|
||||
config = Config.instance()
|
||||
subnet = getattr(config.settings.WebWireshark, "network_subnet", "172.31.0.0/22")
|
||||
logger.info(f"Creating network {self.network_name} with subnet {subnet}")
|
||||
await self.docker.create_network(
|
||||
self.network_name,
|
||||
driver="bridge",
|
||||
subnet=subnet
|
||||
)
|
||||
await self.docker.create_network(self.network_name, driver="bridge", subnet=subnet)
|
||||
|
||||
async def get_or_create_container(
|
||||
self,
|
||||
@@ -640,7 +628,7 @@ class WebWiresharkManager:
|
||||
memory: str = "2g",
|
||||
memory_swap: str = None,
|
||||
cpus: float = 1.0,
|
||||
pids_limit: int = 1000
|
||||
pids_limit: int = 1000,
|
||||
) -> str:
|
||||
"""Get or create project's Web Wireshark container.
|
||||
|
||||
@@ -716,21 +704,15 @@ class WebWiresharkManager:
|
||||
"PidsLimit": pids_limit,
|
||||
"RestartPolicy": {"Name": "unless-stopped"},
|
||||
"Init": True, # Use init system (tini) as PID 1 to reap zombie processes
|
||||
"LogConfig": {
|
||||
"Type": "json-file",
|
||||
"Config": {
|
||||
"max-size": "10m",
|
||||
"max-file": "3"
|
||||
}
|
||||
}
|
||||
"LogConfig": {"Type": "json-file", "Config": {"max-size": "10m", "max-file": "3"}},
|
||||
}
|
||||
|
||||
# Health check configuration
|
||||
health_config = {
|
||||
"Test": ["CMD-SHELL", "xpra list"],
|
||||
"Interval": 30000000000, # 30 seconds (nanoseconds)
|
||||
"Timeout": 10000000000, # 10 seconds
|
||||
"Retries": 3
|
||||
"Timeout": 10000000000, # 10 seconds
|
||||
"Retries": 3,
|
||||
}
|
||||
|
||||
try:
|
||||
@@ -740,11 +722,7 @@ class WebWiresharkManager:
|
||||
network=self.network_name,
|
||||
host_config=host_config,
|
||||
health_config=health_config,
|
||||
environment={
|
||||
"XDG_RUNTIME_DIR": "/run/user/1000",
|
||||
"LANG": "C.UTF-8",
|
||||
"LC_ALL": "C.UTF-8"
|
||||
}
|
||||
environment={"XDG_RUNTIME_DIR": "/run/user/1000", "LANG": "C.UTF-8", "LC_ALL": "C.UTF-8"},
|
||||
)
|
||||
except RuntimeError as e:
|
||||
error_msg = str(e)
|
||||
@@ -773,7 +751,7 @@ class WebWiresharkManager:
|
||||
memory: str = "2g",
|
||||
memory_swap: Optional[str] = None,
|
||||
cpus: float = 1.0,
|
||||
pids_limit: int = 1000
|
||||
pids_limit: int = 1000,
|
||||
) -> dict:
|
||||
"""Start Web Wireshark session.
|
||||
|
||||
@@ -799,20 +777,10 @@ class WebWiresharkManager:
|
||||
# Auto-detect capture_stream_url if not provided
|
||||
if not capture_stream_url:
|
||||
gns3_url = self.detect_gns3_url()
|
||||
capture_stream_url = (
|
||||
f"{gns3_url}/v3/projects/{project_id}/links/"
|
||||
f"{link_id}/capture/stream"
|
||||
)
|
||||
capture_stream_url = f"{gns3_url}/v3/projects/{project_id}/links/{link_id}/capture/stream"
|
||||
logger.info(f"Auto-detected capture stream URL: {capture_stream_url}")
|
||||
|
||||
container_id = await self.get_or_create_container(
|
||||
project_id,
|
||||
image,
|
||||
memory,
|
||||
memory_swap,
|
||||
cpus,
|
||||
pids_limit
|
||||
)
|
||||
container_id = await self.get_or_create_container(project_id, image, memory, memory_swap, cpus, pids_limit)
|
||||
container_name = f"gns3-wireshark-{project_id}"
|
||||
|
||||
# Fix localhost URL after we have container_id
|
||||
@@ -828,13 +796,15 @@ class WebWiresharkManager:
|
||||
has_process_residuals, has_socket_residuals = await self._check_residuals_exist(container_id, display)
|
||||
|
||||
if has_process_residuals or has_socket_residuals:
|
||||
logger.info(f"Found residual processes={has_process_residuals} sockets={has_socket_residuals} on display :{display}, cleaning up...")
|
||||
logger.info(
|
||||
f"Found residual processes={has_process_residuals} sockets={has_socket_residuals} on display :{display}, cleaning up..."
|
||||
)
|
||||
if has_process_residuals:
|
||||
patterns = [
|
||||
f'xpra.*:{display}',
|
||||
f'Xvfb.*:{display}',
|
||||
f'wireshark.*:{display}',
|
||||
f'pulseaudio.*display=:{display}'
|
||||
f"xpra.*:{display}",
|
||||
f"Xvfb.*:{display}",
|
||||
f"wireshark.*:{display}",
|
||||
f"pulseaudio.*display=:{display}",
|
||||
]
|
||||
await self._kill_process_tree_batch(container_id, patterns)
|
||||
if has_socket_residuals:
|
||||
@@ -848,29 +818,24 @@ class WebWiresharkManager:
|
||||
|
||||
xpra_cmd = [
|
||||
"XPRA_CLIENT_CAN_SHUTDOWN=false",
|
||||
"xpra", "start", f":{display}",
|
||||
"xpra",
|
||||
"start",
|
||||
f":{display}",
|
||||
'--xvfb="Xvfb -screen 0 1920x1080x24 +extension RANDR"',
|
||||
"--html=off",
|
||||
f"--bind-ws=0.0.0.0:{port}",
|
||||
f"--session-name={session_name}",
|
||||
"--daemon=yes",
|
||||
"--dbus-launch=no",
|
||||
"--resize-display=yes"
|
||||
"--resize-display=yes",
|
||||
]
|
||||
|
||||
# Parallel execution: get container info + start xpra
|
||||
container_info_task = asyncio.create_task(
|
||||
self.docker.get_container(container_name)
|
||||
)
|
||||
xpra_start_task = asyncio.create_task(
|
||||
self._exec_in_container(container_id, " ".join(xpra_cmd))
|
||||
)
|
||||
container_info_task = asyncio.create_task(self.docker.get_container(container_name))
|
||||
xpra_start_task = asyncio.create_task(self._exec_in_container(container_id, " ".join(xpra_cmd)))
|
||||
|
||||
# Wait for both tasks to complete
|
||||
container, (returncode, stdout, stderr) = await asyncio.gather(
|
||||
container_info_task,
|
||||
xpra_start_task
|
||||
)
|
||||
container, (returncode, stdout, stderr) = await asyncio.gather(container_info_task, xpra_start_task)
|
||||
|
||||
# Check xpra start result
|
||||
if returncode != 0:
|
||||
@@ -901,10 +866,14 @@ class WebWiresharkManager:
|
||||
|
||||
# Execute Wireshark command without waiting for completion
|
||||
await asyncio.create_subprocess_exec(
|
||||
"docker", "exec", container_id,
|
||||
"bash", "-c", wireshark_cmd,
|
||||
"docker",
|
||||
"exec",
|
||||
container_id,
|
||||
"bash",
|
||||
"-c",
|
||||
wireshark_cmd,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
)
|
||||
|
||||
# Don't wait - Wireshark runs in background
|
||||
@@ -918,7 +887,7 @@ class WebWiresharkManager:
|
||||
"container_name": container_name,
|
||||
"container_id": container_id,
|
||||
"session_name": session_name,
|
||||
"capture_stream_url": capture_stream_url
|
||||
"capture_stream_url": capture_stream_url,
|
||||
}
|
||||
|
||||
logger.info(f"Web Wireshark session started successfully: {result['ws_url']}")
|
||||
@@ -947,11 +916,11 @@ class WebWiresharkManager:
|
||||
# This ensures child processes are properly terminated, not left as zombies
|
||||
logger.info(f"Stopping all processes on display :{display}")
|
||||
patterns = [
|
||||
f'xpra.*:{display}',
|
||||
f'Xvfb.*:{display}',
|
||||
f'Xvfb-for-Xpra-{display}',
|
||||
f'wireshark.*:{display}',
|
||||
f'pulseaudio.*display=:{display}'
|
||||
f"xpra.*:{display}",
|
||||
f"Xvfb.*:{display}",
|
||||
f"Xvfb-for-Xpra-{display}",
|
||||
f"wireshark.*:{display}",
|
||||
f"pulseaudio.*display=:{display}",
|
||||
]
|
||||
await self._kill_process_tree_batch(container["Id"], patterns)
|
||||
await self._cleanup_x_lock(container["Id"], display)
|
||||
@@ -971,7 +940,7 @@ class WebWiresharkManager:
|
||||
memory: str = "2g",
|
||||
memory_swap: str = None,
|
||||
cpus: float = 1.0,
|
||||
pids_limit: int = 1000
|
||||
pids_limit: int = 1000,
|
||||
):
|
||||
"""Restart Web Wireshark session.
|
||||
|
||||
@@ -1001,7 +970,7 @@ class WebWiresharkManager:
|
||||
memory=memory,
|
||||
memory_swap=memory_swap,
|
||||
cpus=cpus,
|
||||
pids_limit=pids_limit
|
||||
pids_limit=pids_limit,
|
||||
)
|
||||
|
||||
async def stop_all_sessions(self, project_id: str):
|
||||
@@ -1021,12 +990,7 @@ class WebWiresharkManager:
|
||||
|
||||
# Kill all wireshark, xpra and Xvfb processes for link sessions
|
||||
# This ensures clean removal of all session processes
|
||||
patterns = [
|
||||
"xpra.*--session-name=link-",
|
||||
"Xvfb-for-Xpra-",
|
||||
"wireshark.*display :",
|
||||
"pulseaudio.*display :"
|
||||
]
|
||||
patterns = ["xpra.*--session-name=link-", "Xvfb-for-Xpra-", "wireshark.*display :", "pulseaudio.*display :"]
|
||||
await self._kill_process_tree_batch(container["Id"], patterns)
|
||||
|
||||
logger.info(f"All Web Wireshark sessions stopped for project {project_id}")
|
||||
|
||||
@@ -62,11 +62,7 @@ def check_docker():
|
||||
|
||||
# Check if Docker daemon is running
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["docker", "info"],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
result = subprocess.run(["docker", "info"], capture_output=True, text=True)
|
||||
if result.returncode != 0:
|
||||
print("Error: Docker daemon is not running", file=sys.stderr)
|
||||
print("Please start Docker and try again", file=sys.stderr)
|
||||
@@ -82,11 +78,7 @@ def image_exists():
|
||||
"""Check if the Docker image already exists."""
|
||||
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["docker", "image", "inspect", DOCKER_IMAGE],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
result = subprocess.run(["docker", "image", "inspect", DOCKER_IMAGE], capture_output=True, text=True)
|
||||
return result.returncode == 0
|
||||
except Exception:
|
||||
return False
|
||||
@@ -115,11 +107,7 @@ def pull_image():
|
||||
print(f"Pulling Docker image: {DOCKER_IMAGE}")
|
||||
print("-" * 60)
|
||||
|
||||
result = subprocess.run(
|
||||
["docker", "pull", DOCKER_IMAGE],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
result = subprocess.run(["docker", "pull", DOCKER_IMAGE], capture_output=True, text=True)
|
||||
|
||||
# Store output for network error detection
|
||||
pull_output = result.stdout + result.stderr
|
||||
@@ -140,33 +128,17 @@ def build_image(dockerfile_path):
|
||||
print("-" * 60)
|
||||
|
||||
result = subprocess.run(
|
||||
["docker", "build", "-t", DOCKER_IMAGE, "-f", dockerfile_path, "."],
|
||||
cwd=dockerfile_dir,
|
||||
pass_fds=(1, 2)
|
||||
["docker", "build", "-t", DOCKER_IMAGE, "-f", dockerfile_path, "."], cwd=dockerfile_dir, pass_fds=(1, 2)
|
||||
)
|
||||
|
||||
return result.returncode == 0
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Setup Web Wireshark Docker image for GNS3"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--force",
|
||||
action="store_true",
|
||||
help="Force rebuild even if image exists"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--build-only",
|
||||
action="store_true",
|
||||
help="Only build locally, skip pull"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--pull-only",
|
||||
action="store_true",
|
||||
help="Only pull from registry, skip build"
|
||||
)
|
||||
parser = argparse.ArgumentParser(description="Setup Web Wireshark Docker image for GNS3")
|
||||
parser.add_argument("--force", action="store_true", help="Force rebuild even if image exists")
|
||||
parser.add_argument("--build-only", action="store_true", help="Only build locally, skip pull")
|
||||
parser.add_argument("--pull-only", action="store_true", help="Only pull from registry, skip build")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
@@ -183,7 +155,7 @@ def main():
|
||||
if image_exists() and not args.force:
|
||||
print(f"Image {DOCKER_IMAGE} already exists.")
|
||||
response = input("Do you want to rebuild it? [y/N]: ").strip().lower()
|
||||
if response != 'y':
|
||||
if response != "y":
|
||||
print("Setup cancelled.")
|
||||
sys.exit(0)
|
||||
print()
|
||||
|
||||
@@ -30,12 +30,7 @@ async def collect_webwireshark_stats(projects: List) -> Dict:
|
||||
"""
|
||||
from .manager import WebWiresharkManager
|
||||
|
||||
stats = {
|
||||
"total_containers": 0,
|
||||
"running_containers": 0,
|
||||
"active_sessions": 0,
|
||||
"containers": []
|
||||
}
|
||||
stats = {"total_containers": 0, "running_containers": 0, "active_sessions": 0, "containers": []}
|
||||
|
||||
# Create a single manager instance and reuse it
|
||||
manager = WebWiresharkManager()
|
||||
@@ -70,15 +65,14 @@ async def collect_webwireshark_stats(projects: List) -> Dict:
|
||||
pids_limit = host_config.get("PidsLimit", 0)
|
||||
|
||||
# Count active capture sessions
|
||||
active_sessions = sum(
|
||||
1 for link in project.links.values()
|
||||
if getattr(link, "capturing", False)
|
||||
)
|
||||
active_sessions = sum(1 for link in project.links.values() if getattr(link, "capturing", False))
|
||||
stats["active_sessions"] += active_sessions
|
||||
container_info["active_sessions"] = active_sessions
|
||||
|
||||
# Add resource limits
|
||||
container_info["memory_limit"] = f"{memory_limit / (1024**3):.1f} GB" if memory_limit > 0 else "unlimited"
|
||||
container_info["memory_limit"] = (
|
||||
f"{memory_limit / (1024**3):.1f} GB" if memory_limit > 0 else "unlimited"
|
||||
)
|
||||
container_info["cpu_limit"] = f"{cpu_quota / 1000000000:.1f}" if cpu_quota > 0 else "unlimited"
|
||||
container_info["pids_limit"] = pids_limit if pids_limit > 0 else "unlimited"
|
||||
|
||||
@@ -111,21 +105,15 @@ async def _get_container_resource_stats(container_id: str) -> Optional[Dict]:
|
||||
"""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["docker", "stats", "--no-stream", "--format",
|
||||
"{{.MemUsage}}\t{{.CPUPerc}}\t{{.PIDs}}",
|
||||
container_id],
|
||||
["docker", "stats", "--no-stream", "--format", "{{.MemUsage}}\t{{.CPUPerc}}\t{{.PIDs}}", container_id],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=2
|
||||
timeout=2,
|
||||
)
|
||||
if result.returncode == 0:
|
||||
parts = result.stdout.strip().split("\t")
|
||||
if len(parts) >= 3:
|
||||
return {
|
||||
"memory": parts[0],
|
||||
"cpu": parts[1],
|
||||
"pids": int(parts[2])
|
||||
}
|
||||
return {"memory": parts[0], "cpu": parts[1], "pids": int(parts[2])}
|
||||
except subprocess.TimeoutExpired:
|
||||
logger.debug(f"Docker stats timeout for container {container_id[:12]}")
|
||||
except Exception as e:
|
||||
|
||||
Reference in new issue
Block a user