Skip to content

Commit f19ff9d

Browse files
virlostmikuska
authored andcommitted
CMLDEV-786 - switch to using link ID as capture key (#188)
* CMLDEV-786 - switch to using link ID as capture key * Remove the never-released capture key method
1 parent c1b9e59 commit f19ff9d

File tree

3 files changed

+44
-105
lines changed

3 files changed

+44
-105
lines changed

tests/test_pcap.py

Lines changed: 19 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,7 @@ def mock_link():
4242

4343
def test_url_templates_exist():
4444
"""Test that all required URL templates are defined."""
45-
required_templates = [
46-
"capture_start",
47-
"capture_stop",
48-
"capture_status",
49-
"capture_key",
50-
]
45+
required_templates = ["capture_start", "capture_stop", "capture_status"]
5146

5247
for template in required_templates:
5348
assert template in Link._URL_TEMPLATES
@@ -59,7 +54,7 @@ def test_start_capture_with_params(mock_link):
5954
"""Test start_capture with explicit parameters."""
6055
expected_response = {
6156
"config": {
62-
"link_capture_key": "test-key-123",
57+
"link_capture_key": mock_link.id,
6358
"maxpackets": 100,
6459
"encap": "ethernet",
6560
},
@@ -73,15 +68,15 @@ def test_start_capture_with_params(mock_link):
7368

7469
assert result == expected_response
7570
assert result["config"]["maxpackets"] == 100
76-
assert "link_capture_key" in result["config"]
71+
assert result["config"]["link_capture_key"] == mock_link.id
7772

7873

7974
@respx.mock
8075
def test_start_capture_defaults(mock_link):
8176
"""Test start_capture without parameters uses server defaults."""
8277
expected_response = {
8378
"config": {
84-
"link_capture_key": "default-key-456",
79+
"link_capture_key": mock_link.id,
8580
"maxpackets": 1000000,
8681
"maxtime": 86400,
8782
"encap": "ethernet",
@@ -97,14 +92,15 @@ def test_start_capture_defaults(mock_link):
9792
assert result == expected_response
9893
assert result["config"]["maxpackets"] == 1000000
9994
assert result["config"]["maxtime"] == 86400
95+
assert result["config"]["link_capture_key"] == mock_link.id
10096

10197

10298
@respx.mock
10399
def test_capture_status(mock_link):
104100
"""Test capture_status with mocked HTTP call."""
105101
expected_status = {
106102
"config": {
107-
"link_capture_key": "status-key-456",
103+
"link_capture_key": mock_link.id,
108104
"maxpackets": 200,
109105
"encap": "ethernet",
110106
},
@@ -118,17 +114,7 @@ def test_capture_status(mock_link):
118114

119115
assert result == expected_status
120116
assert result["packetscaptured"] == 15
121-
122-
123-
@respx.mock
124-
def test_capture_key(mock_link):
125-
"""Test capture_key with mocked HTTP call."""
126-
expected_key = "capture-key-789"
127-
mock_link._session.get.return_value.json.return_value = expected_key
128-
129-
result = mock_link.capture_key()
130-
131-
assert result == expected_key
117+
assert result["config"]["link_capture_key"] == mock_link.id
132118

133119

134120
@respx.mock
@@ -143,23 +129,15 @@ def test_stop_capture(mock_link):
143129

144130

145131
@respx.mock
146-
def test_download_capture_auto_key(mock_link):
147-
"""Test download_capture with automatic key retrieval."""
148-
149-
def mock_get_side_effect(url):
150-
mock_response = Mock()
151-
if "capture/key" in url:
152-
mock_response.json.return_value = "auto-retrieved-key"
153-
else:
154-
mock_response.content = b"PCAP file content"
155-
return mock_response
156-
157-
mock_link._session.get.side_effect = mock_get_side_effect
132+
def test_download_capture(mock_link):
133+
"""Test download_capture."""
134+
expected_content = b"PCAP file content"
135+
mock_link._session.get.return_value.content = expected_content
158136

159137
result = mock_link.download_capture()
160138

161-
assert result == b"PCAP file content"
162-
assert mock_link._session.get.call_count == 2
139+
mock_link._session.get.assert_called_once()
140+
assert result == expected_content
163141

164142

165143
@respx.mock
@@ -169,16 +147,7 @@ def test_get_capture_packets(mock_link):
169147
{"packet": {"timestamp": "2026-01-12T10:00:01Z", "size": 64}},
170148
{"packet": {"timestamp": "2026-01-12T10:00:02Z", "size": 128}},
171149
]
172-
173-
def mock_get_side_effect(url):
174-
mock_response = Mock()
175-
if "capture/key" in url:
176-
mock_response.json.return_value = "packet-key-123"
177-
else:
178-
mock_response.json.return_value = expected_packets
179-
return mock_response
180-
181-
mock_link._session.get.side_effect = mock_get_side_effect
150+
mock_link._session.get.return_value.json.return_value = expected_packets
182151

183152
result = mock_link.get_capture_packets()
184153

@@ -187,22 +156,12 @@ def mock_get_side_effect(url):
187156

188157

189158
@respx.mock
190-
def test_download_capture_packet(mock_link):
159+
def test_get_capture_packet(mock_link):
191160
"""Test download_capture_packet with mocked HTTP call."""
192-
expected_packet_data = {
193-
"packet": {"timestamp": "2026-01-12T10:00:05Z", "size": 256}
194-
}
195-
196-
def mock_get_side_effect(url):
197-
mock_response = Mock()
198-
if "capture/key" in url:
199-
mock_response.json.return_value = "packet-download-key"
200-
else:
201-
mock_response.json.return_value = expected_packet_data
202-
return mock_response
203-
204-
mock_link._session.get.side_effect = mock_get_side_effect
161+
# the actual PDML is rather large
162+
expected_packet_data = {"proto": []}
163+
mock_link._session.get.return_value.json.return_value = expected_packet_data
205164

206-
result = mock_link.download_capture_packet(packet_id=5)
165+
result = mock_link.get_capture_packet(packet_id=5)
207166

208167
assert result == expected_packet_data

virl2_client/event_handling.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -313,8 +313,7 @@ def _handle_element_modified(self, event: Event) -> None:
313313
event.element._update(event.data, push_to_server=False)
314314

315315
elif event.element_type == "link":
316-
# only sends link_capture_key which is not used by the client,
317-
# so we discard the message
316+
# not used by the client so we discard the message
318317
pass
319318

320319
else:

virl2_client/models/link.py

Lines changed: 24 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
import logging
2424
import time
25+
import warnings
2526
from typing import TYPE_CHECKING
2627

2728
from ..utils import UNCHANGED, _Sentinel, check_stale, get_url_from_template, locked
@@ -48,7 +49,9 @@ class Link:
4849
"capture_start": "{lab}/links/{id}/capture/start",
4950
"capture_stop": "{lab}/links/{id}/capture/stop",
5051
"capture_status": "{lab}/links/{id}/capture/status",
51-
"capture_key": "{lab}/links/{id}/capture/key",
52+
"pcap_file": "{pcap}/{id}",
53+
"pcap_packets": "{pcap}/{id}/packets",
54+
"pcap_packet": "{pcap}/{id}/packets/{packet_id}",
5255
}
5356

5457
def __init__(
@@ -113,8 +116,11 @@ def _url_for(self, endpoint: str, **kwargs):
113116
:param **kwargs: Keyword arguments used to format the URL.
114117
:returns: The formatted URL.
115118
"""
116-
kwargs["lab"] = self._lab._url_for("lab")
117-
kwargs["id"] = self.id
119+
if endpoint.startswith("pcap"):
120+
kwargs["pcap"] = f"{self._session.base_url}/api/v0/pcap"
121+
else:
122+
kwargs["lab"] = self._lab._url_for("lab")
123+
kwargs["id"] = self._id
118124
return get_url_from_template(endpoint, self._URL_TEMPLATES, kwargs)
119125

120126
@property
@@ -442,7 +448,7 @@ def start_capture(
442448
if bpfilter is not None:
443449
data["bpfilter"] = bpfilter
444450

445-
_LOGGER.info(f"Starting packet capture on link {self.id}")
451+
_LOGGER.info(f"Starting packet capture on link {self._id}")
446452
return self._session.put(url, json=data).json()
447453

448454
@check_stale
@@ -451,7 +457,7 @@ def stop_capture(self) -> None:
451457
Stop the packet capture on this link.
452458
"""
453459
url = self._url_for("capture_stop")
454-
_LOGGER.info(f"Stopping packet capture on link {self.id}")
460+
_LOGGER.info(f"Stopping packet capture on link {self._id}")
455461
self._session.put(url)
456462

457463
@check_stale
@@ -464,58 +470,33 @@ def capture_status(self) -> dict:
464470
url = self._url_for("capture_status")
465471
return self._session.get(url).json()
466472

467-
@check_stale
468-
def capture_key(self) -> str:
469-
"""
470-
Get the capture key (UUID) for the packet capture on this link.
471-
472-
:returns: The capture key as a string.
473-
:raises: HTTP exception if no capture is running on this link.
474-
"""
475-
url = self._url_for("capture_key")
476-
return self._session.get(url).json()
477-
478-
def download_capture(self, capture_key: str | None = None) -> bytes:
473+
def download_capture(self) -> bytes:
479474
"""
480-
Download the PCAP file for this link's capture.
475+
Download the PCAP file for this link's last capture.
481476
482-
:param capture_key: The capture key. If None, will fetch it automatically.
483477
:returns: The PCAP file content as bytes.
484478
"""
485-
if capture_key is None:
486-
capture_key = self.capture_key()
487-
488-
url = f"{self._lab._session.base_url}/api/v0/pcap/{capture_key}"
489-
_LOGGER.info(f"Downloading PCAP for capture key {capture_key}")
479+
url = self._url_for("pcap_file")
480+
_LOGGER.info(f"Downloading PCAP for link {self._id}")
490481
return self._session.get(url).content
491482

492-
def get_capture_packets(self, capture_key: str | None = None) -> list[dict]:
483+
def get_capture_packets(self) -> list[dict]:
493484
"""
494-
Get a list of all captured packets in decoded format.
485+
Get a list of all captured packets in decoded format from last capture.
495486
496-
:param capture_key: The capture key. If None, will fetch it automatically.
497487
:returns: List of packet dictionaries with decoded packet information.
498488
"""
499-
if capture_key is None:
500-
capture_key = self.capture_key()
501-
502-
url = f"{self._lab._session.base_url}/api/v0/pcap/{capture_key}/packets"
503-
_LOGGER.info(f"Getting packet list for capture key {capture_key}")
489+
url = self._url_for("pcap_packets")
490+
_LOGGER.info(f"Getting packet list for link {self._id}")
504491
return self._session.get(url).json()
505492

506-
def download_capture_packet(
507-
self, packet_id: int, capture_key: str | None = None
508-
) -> dict:
493+
def get_capture_packet(self, packet_id: int) -> dict:
509494
"""
510-
Download a specific packet from the capture in decoded format.
495+
Get a specific packet from the last capture in decoded format.
511496
512-
:param packet_id: The ID of the packet to download (1-based).
513-
:param capture_key: The capture key. If None, will fetch it automatically.
497+
:param packet_id: The ID of the packet (1-based index).
514498
:returns: Dictionary containing the decoded packet information.
515499
"""
516-
if capture_key is None:
517-
capture_key = self.capture_key()
518-
519-
url = f"{self._lab._session.base_url}/api/v0/pcap/{capture_key}/packet/{packet_id}"
520-
_LOGGER.info(f"Downloading packet {packet_id} for capture key {capture_key}")
500+
url = self._url_for("pcap_packet", packet_id=packet_id)
501+
_LOGGER.info(f"Downloading packet {packet_id} for link {self._id}")
521502
return self._session.get(url).json()

0 commit comments

Comments
 (0)