mirror of
https://github.com/GNS3/gns3-server
synced 2024-11-24 09:18:08 +00:00
Refactor how clients access PCAP capture files. Fixes https://github.com/GNS3/gns3-gui/issues/2438.
* The PCAP file is directly accessed if controller and client are on the same host. * The PCAP file is streamed from the compute server to the client with the controller as a proxy when the controller is remote for the client.
This commit is contained in:
parent
bf1b801cc0
commit
2764828f38
@ -428,6 +428,47 @@ class BaseManager:
|
||||
assert nio is not None
|
||||
return nio
|
||||
|
||||
async def stream_pcap_file(self, nio, project_id, request, response):
|
||||
"""
|
||||
Streams a PCAP file.
|
||||
|
||||
:param nio: NIO object
|
||||
:param project_id: Project identifier
|
||||
:param request: request object
|
||||
:param response: response object
|
||||
"""
|
||||
|
||||
if not nio.capturing:
|
||||
raise aiohttp.web.HTTPConflict(text="Nothing to stream because there is no packet capture active")
|
||||
|
||||
project = ProjectManager.instance().get_project(project_id)
|
||||
path = os.path.normpath(os.path.join(project.capture_working_directory(), nio.pcap_output_file))
|
||||
# Raise an error if user try to escape
|
||||
#if path[0] == ".":
|
||||
# raise aiohttp.web.HTTPForbidden()
|
||||
#path = os.path.join(project.path, path)
|
||||
|
||||
response.content_type = "application/vnd.tcpdump.pcap"
|
||||
response.set_status(200)
|
||||
response.enable_chunked_encoding()
|
||||
|
||||
try:
|
||||
with open(path, "rb") as f:
|
||||
await response.prepare(request)
|
||||
while nio.capturing:
|
||||
data = f.read(4096)
|
||||
if not data:
|
||||
await asyncio.sleep(0.1)
|
||||
continue
|
||||
try:
|
||||
await response.write(data)
|
||||
except ConnectionError:
|
||||
break
|
||||
except FileNotFoundError:
|
||||
raise aiohttp.web.HTTPNotFound()
|
||||
except PermissionError:
|
||||
raise aiohttp.web.HTTPForbidden()
|
||||
|
||||
def get_abs_image_path(self, path):
|
||||
"""
|
||||
Get the absolute path of an image
|
||||
|
@ -16,7 +16,6 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import sys
|
||||
import asyncio
|
||||
import subprocess
|
||||
|
||||
from ...error import NodeError
|
||||
@ -461,13 +460,13 @@ class Cloud(BaseNode):
|
||||
await self.start()
|
||||
return nio
|
||||
|
||||
async def start_capture(self, port_number, output_file, data_link_type="DLT_EN10MB"):
|
||||
def get_nio(self, port_number):
|
||||
"""
|
||||
Starts a packet capture.
|
||||
Gets a port NIO binding.
|
||||
|
||||
:param port_number: allocated port number
|
||||
:param output_file: PCAP destination file for the capture
|
||||
:param data_link_type: PCAP data link type (DLT_*), default is DLT_EN10MB
|
||||
:param port_number: port number
|
||||
|
||||
:returns: NIO instance
|
||||
"""
|
||||
|
||||
if not [port["port_number"] for port in self._ports_mapping if port_number == port["port_number"]]:
|
||||
@ -479,6 +478,18 @@ class Cloud(BaseNode):
|
||||
|
||||
nio = self._nios[port_number]
|
||||
|
||||
return nio
|
||||
|
||||
async def start_capture(self, port_number, output_file, data_link_type="DLT_EN10MB"):
|
||||
"""
|
||||
Starts a packet capture.
|
||||
|
||||
:param port_number: allocated port number
|
||||
:param output_file: PCAP destination file for the capture
|
||||
:param data_link_type: PCAP data link type (DLT_*), default is DLT_EN10MB
|
||||
"""
|
||||
|
||||
nio = self.get_nio(port_number)
|
||||
if nio.capturing:
|
||||
raise NodeError("Packet capture is already activated on port {port_number}".format(port_number=port_number))
|
||||
nio.startPacketCapture(output_file)
|
||||
@ -496,14 +507,7 @@ class Cloud(BaseNode):
|
||||
:param port_number: allocated port number
|
||||
"""
|
||||
|
||||
if not [port["port_number"] for port in self._ports_mapping if port_number == port["port_number"]]:
|
||||
raise NodeError("Port {port_number} doesn't exist on cloud '{name}'".format(name=self.name,
|
||||
port_number=port_number))
|
||||
|
||||
if port_number not in self._nios:
|
||||
raise NodeError("Port {} is not connected".format(port_number))
|
||||
|
||||
nio = self._nios[port_number]
|
||||
nio = self.get_nio(port_number)
|
||||
nio.stopPacketCapture()
|
||||
bridge_name = "{}-{}".format(self._id, port_number)
|
||||
await self._ubridge_send("bridge stop_capture {name}".format(name=bridge_name))
|
||||
|
@ -853,10 +853,10 @@ class DockerVM(BaseNode):
|
||||
|
||||
async def adapter_update_nio_binding(self, adapter_number, nio):
|
||||
"""
|
||||
Update a port NIO binding.
|
||||
Update an adapter NIO binding.
|
||||
|
||||
:param adapter_number: adapter number
|
||||
:param nio: NIO instance to add to the adapter
|
||||
:param nio: NIO instance to update the adapter
|
||||
"""
|
||||
|
||||
if self.ubridge:
|
||||
@ -895,6 +895,28 @@ class DockerVM(BaseNode):
|
||||
nio=adapter.host_ifc,
|
||||
adapter_number=adapter_number))
|
||||
|
||||
def get_nio(self, adapter_number):
|
||||
"""
|
||||
Gets an adapter NIO binding.
|
||||
|
||||
:param adapter_number: adapter number
|
||||
|
||||
:returns: NIO instance
|
||||
"""
|
||||
|
||||
try:
|
||||
adapter = self._ethernet_adapters[adapter_number]
|
||||
except KeyError:
|
||||
raise DockerError("Adapter {adapter_number} doesn't exist on Docker VM '{name}'".format(name=self.name,
|
||||
adapter_number=adapter_number))
|
||||
|
||||
nio = adapter.get_nio(0)
|
||||
|
||||
if not nio:
|
||||
raise DockerError("Adapter {} is not connected".format(adapter_number))
|
||||
|
||||
return nio
|
||||
|
||||
@property
|
||||
def adapters(self):
|
||||
"""
|
||||
@ -967,17 +989,7 @@ class DockerVM(BaseNode):
|
||||
:param output_file: PCAP destination file for the capture
|
||||
"""
|
||||
|
||||
try:
|
||||
adapter = self._ethernet_adapters[adapter_number]
|
||||
except KeyError:
|
||||
raise DockerError("Adapter {adapter_number} doesn't exist on Docker VM '{name}'".format(name=self.name,
|
||||
adapter_number=adapter_number))
|
||||
|
||||
nio = adapter.get_nio(0)
|
||||
|
||||
if not nio:
|
||||
raise DockerError("Adapter {} is not connected".format(adapter_number))
|
||||
|
||||
nio = self.get_nio(adapter_number)
|
||||
if nio.capturing:
|
||||
raise DockerError("Packet capture is already activated on adapter {adapter_number}".format(adapter_number=adapter_number))
|
||||
|
||||
@ -997,19 +1009,8 @@ class DockerVM(BaseNode):
|
||||
:param adapter_number: adapter number
|
||||
"""
|
||||
|
||||
try:
|
||||
adapter = self._ethernet_adapters[adapter_number]
|
||||
except KeyError:
|
||||
raise DockerError("Adapter {adapter_number} doesn't exist on Docker VM '{name}'".format(name=self.name,
|
||||
adapter_number=adapter_number))
|
||||
|
||||
nio = adapter.get_nio(0)
|
||||
|
||||
if not nio:
|
||||
raise DockerError("Adapter {} is not connected".format(adapter_number))
|
||||
|
||||
nio = self.get_nio(adapter_number)
|
||||
nio.stopPacketCapture()
|
||||
|
||||
if self.status == "started" and self.ubridge:
|
||||
await self._stop_ubridge_capture(adapter_number)
|
||||
|
||||
|
@ -223,6 +223,25 @@ class ATMSwitch(Device):
|
||||
del self._nios[port_number]
|
||||
return nio
|
||||
|
||||
def get_nio(self, port_number):
|
||||
"""
|
||||
Gets a port NIO binding.
|
||||
|
||||
:param port_number: port number
|
||||
|
||||
:returns: NIO instance
|
||||
"""
|
||||
|
||||
if port_number not in self._nios:
|
||||
raise DynamipsError("Port {} is not allocated".format(port_number))
|
||||
|
||||
nio = self._nios[port_number]
|
||||
|
||||
if not nio:
|
||||
raise DynamipsError("Port {} is not connected".format(port_number))
|
||||
|
||||
return nio
|
||||
|
||||
async def set_mappings(self, mappings):
|
||||
"""
|
||||
Applies VC mappings
|
||||
@ -424,11 +443,7 @@ class ATMSwitch(Device):
|
||||
:param data_link_type: PCAP data link type (DLT_*), default is DLT_ATM_RFC1483
|
||||
"""
|
||||
|
||||
if port_number not in self._nios:
|
||||
raise DynamipsError("Port {} is not allocated".format(port_number))
|
||||
|
||||
nio = self._nios[port_number]
|
||||
|
||||
nio = self.get_nio(port_number)
|
||||
data_link_type = data_link_type.lower()
|
||||
if data_link_type.startswith("dlt_"):
|
||||
data_link_type = data_link_type[4:]
|
||||
@ -450,10 +465,7 @@ class ATMSwitch(Device):
|
||||
:param port_number: allocated port number
|
||||
"""
|
||||
|
||||
if port_number not in self._nios:
|
||||
raise DynamipsError("Port {} is not allocated".format(port_number))
|
||||
|
||||
nio = self._nios[port_number]
|
||||
nio = self.get_nio(port_number)
|
||||
await nio.unbind_filter("both")
|
||||
log.info('ATM switch "{name}" [{id}]: stopping packet capture on port {port}'.format(name=self._name,
|
||||
id=self._id,
|
||||
|
@ -19,8 +19,6 @@
|
||||
Hub object that uses the Bridge interface to create a hub with ports.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
|
||||
from .bridge import Bridge
|
||||
from ..nios.nio_udp import NIOUDP
|
||||
from ..dynamips_error import DynamipsError
|
||||
@ -177,6 +175,25 @@ class EthernetHub(Bridge):
|
||||
del self._mappings[port_number]
|
||||
return nio
|
||||
|
||||
def get_nio(self, port_number):
|
||||
"""
|
||||
Gets a port NIO binding.
|
||||
|
||||
:param port_number: port number
|
||||
|
||||
:returns: NIO instance
|
||||
"""
|
||||
|
||||
if port_number not in self._mappings:
|
||||
raise DynamipsError("Port {} is not allocated".format(port_number))
|
||||
|
||||
nio = self._mappings[port_number]
|
||||
|
||||
if not nio:
|
||||
raise DynamipsError("Port {} is not connected".format(port_number))
|
||||
|
||||
return nio
|
||||
|
||||
async def start_capture(self, port_number, output_file, data_link_type="DLT_EN10MB"):
|
||||
"""
|
||||
Starts a packet capture.
|
||||
@ -186,11 +203,7 @@ class EthernetHub(Bridge):
|
||||
:param data_link_type: PCAP data link type (DLT_*), default is DLT_EN10MB
|
||||
"""
|
||||
|
||||
if port_number not in self._mappings:
|
||||
raise DynamipsError("Port {} is not allocated".format(port_number))
|
||||
|
||||
nio = self._mappings[port_number]
|
||||
|
||||
nio = self.get_nio(port_number)
|
||||
data_link_type = data_link_type.lower()
|
||||
if data_link_type.startswith("dlt_"):
|
||||
data_link_type = data_link_type[4:]
|
||||
@ -212,10 +225,7 @@ class EthernetHub(Bridge):
|
||||
:param port_number: allocated port number
|
||||
"""
|
||||
|
||||
if port_number not in self._mappings:
|
||||
raise DynamipsError("Port {} is not allocated".format(port_number))
|
||||
|
||||
nio = self._mappings[port_number]
|
||||
nio = self.get_nio(port_number)
|
||||
await nio.unbind_filter("both")
|
||||
log.info('Ethernet hub "{name}" [{id}]: stopping packet capture on port {port}'.format(name=self._name,
|
||||
id=self._id,
|
||||
|
@ -300,6 +300,25 @@ class EthernetSwitch(Device):
|
||||
|
||||
return nio
|
||||
|
||||
def get_nio(self, port_number):
|
||||
"""
|
||||
Gets a port NIO binding.
|
||||
|
||||
:param port_number: port number
|
||||
|
||||
:returns: NIO instance
|
||||
"""
|
||||
|
||||
if port_number not in self._nios:
|
||||
raise DynamipsError("Port {} is not allocated".format(port_number))
|
||||
|
||||
nio = self._nios[port_number]
|
||||
|
||||
if not nio:
|
||||
raise DynamipsError("Port {} is not connected".format(port_number))
|
||||
|
||||
return nio
|
||||
|
||||
async def set_port_settings(self, port_number, settings):
|
||||
"""
|
||||
Applies port settings to a specific port.
|
||||
@ -413,14 +432,7 @@ class EthernetSwitch(Device):
|
||||
:param data_link_type: PCAP data link type (DLT_*), default is DLT_EN10MB
|
||||
"""
|
||||
|
||||
if port_number not in self._nios:
|
||||
raise DynamipsError("Port {} is not allocated".format(port_number))
|
||||
|
||||
nio = self._nios[port_number]
|
||||
|
||||
if not nio:
|
||||
raise DynamipsError("Port {} is not connected".format(port_number))
|
||||
|
||||
nio = self.get_nio(port_number)
|
||||
data_link_type = data_link_type.lower()
|
||||
if data_link_type.startswith("dlt_"):
|
||||
data_link_type = data_link_type[4:]
|
||||
@ -442,14 +454,7 @@ class EthernetSwitch(Device):
|
||||
:param port_number: allocated port number
|
||||
"""
|
||||
|
||||
if port_number not in self._nios:
|
||||
raise DynamipsError("Port {} is not allocated".format(port_number))
|
||||
|
||||
nio = self._nios[port_number]
|
||||
|
||||
if not nio:
|
||||
raise DynamipsError("Port {} is not connected".format(port_number))
|
||||
|
||||
nio = self.get_nio(port_number)
|
||||
await nio.unbind_filter("both")
|
||||
log.info('Ethernet switch "{name}" [{id}]: stopping packet capture on port {port}'.format(name=self._name,
|
||||
id=self._id,
|
||||
|
@ -209,6 +209,25 @@ class FrameRelaySwitch(Device):
|
||||
del self._nios[port_number]
|
||||
return nio
|
||||
|
||||
def get_nio(self, port_number):
|
||||
"""
|
||||
Gets a port NIO binding.
|
||||
|
||||
:param port_number: port number
|
||||
|
||||
:returns: NIO instance
|
||||
"""
|
||||
|
||||
if port_number not in self._nios:
|
||||
raise DynamipsError("Port {} is not allocated".format(port_number))
|
||||
|
||||
nio = self._nios[port_number]
|
||||
|
||||
if not nio:
|
||||
raise DynamipsError("Port {} is not connected".format(port_number))
|
||||
|
||||
return nio
|
||||
|
||||
async def set_mappings(self, mappings):
|
||||
"""
|
||||
Applies VC mappings
|
||||
@ -309,10 +328,7 @@ class FrameRelaySwitch(Device):
|
||||
:param data_link_type: PCAP data link type (DLT_*), default is DLT_FRELAY
|
||||
"""
|
||||
|
||||
if port_number not in self._nios:
|
||||
raise DynamipsError("Port {} is not allocated".format(port_number))
|
||||
|
||||
nio = self._nios[port_number]
|
||||
nio = self.get_nio(port_number)
|
||||
|
||||
data_link_type = data_link_type.lower()
|
||||
if data_link_type.startswith("dlt_"):
|
||||
@ -335,10 +351,7 @@ class FrameRelaySwitch(Device):
|
||||
:param port_number: allocated port number
|
||||
"""
|
||||
|
||||
if port_number not in self._nios:
|
||||
raise DynamipsError("Port {} is not allocated".format(port_number))
|
||||
|
||||
nio = self._nios[port_number]
|
||||
nio = self.get_nio(port_number)
|
||||
await nio.unbind_filter("both")
|
||||
log.info('Frame relay switch "{name}" [{id}]: stopping packet capture on port {port}'.format(name=self._name,
|
||||
id=self._id,
|
||||
|
@ -1264,7 +1264,7 @@ class Router(BaseNode):
|
||||
raise DynamipsError("Adapter is missing in slot {slot_number}".format(slot_number=slot_number))
|
||||
|
||||
if not adapter.port_exists(port_number):
|
||||
raise DynamipsError("Port {port_number} does not exist in adapter {adapter}".format(adapter=adapter,
|
||||
raise DynamipsError("Port {port_number} does not exist on adapter {adapter}".format(adapter=adapter,
|
||||
port_number=port_number))
|
||||
|
||||
try:
|
||||
@ -1299,6 +1299,7 @@ class Router(BaseNode):
|
||||
:param port_number: port number
|
||||
:param nio: NIO instance to add to the slot/port
|
||||
"""
|
||||
|
||||
await nio.update()
|
||||
|
||||
async def slot_remove_nio_binding(self, slot_number, port_number):
|
||||
@ -1321,7 +1322,7 @@ class Router(BaseNode):
|
||||
raise DynamipsError("Adapter is missing in slot {slot_number}".format(slot_number=slot_number))
|
||||
|
||||
if not adapter.port_exists(port_number):
|
||||
raise DynamipsError("Port {port_number} does not exist in adapter {adapter}".format(adapter=adapter,
|
||||
raise DynamipsError("Port {port_number} does not exist on adapter {adapter}".format(adapter=adapter,
|
||||
port_number=port_number))
|
||||
|
||||
await self.slot_disable_nio(slot_number, port_number)
|
||||
@ -1362,6 +1363,32 @@ class Router(BaseNode):
|
||||
slot_number=slot_number,
|
||||
port_number=port_number))
|
||||
|
||||
def get_nio(self, slot_number, port_number):
|
||||
"""
|
||||
Gets an slot NIO binding.
|
||||
|
||||
:param slot_number: slot number
|
||||
:param port_number: port number
|
||||
|
||||
:returns: NIO instance
|
||||
"""
|
||||
|
||||
try:
|
||||
adapter = self._slots[slot_number]
|
||||
except IndexError:
|
||||
raise DynamipsError('Slot {slot_number} does not exist on router "{name}"'.format(name=self._name,
|
||||
slot_number=slot_number))
|
||||
if not adapter.port_exists(port_number):
|
||||
raise DynamipsError("Port {port_number} does not exist on adapter {adapter}".format(adapter=adapter,
|
||||
port_number=port_number))
|
||||
|
||||
nio = adapter.get_nio(port_number)
|
||||
|
||||
if not nio:
|
||||
raise DynamipsError("Port {slot_number}/{port_number} is not connected".format(slot_number=slot_number,
|
||||
port_number=port_number))
|
||||
return nio
|
||||
|
||||
async def slot_disable_nio(self, slot_number, port_number):
|
||||
"""
|
||||
Disables a slot NIO binding.
|
||||
@ -1402,7 +1429,7 @@ class Router(BaseNode):
|
||||
raise DynamipsError('Slot {slot_number} does not exist on router "{name}"'.format(name=self._name,
|
||||
slot_number=slot_number))
|
||||
if not adapter.port_exists(port_number):
|
||||
raise DynamipsError("Port {port_number} does not exist in adapter {adapter}".format(adapter=adapter,
|
||||
raise DynamipsError("Port {port_number} does not exist on adapter {adapter}".format(adapter=adapter,
|
||||
port_number=port_number))
|
||||
|
||||
data_link_type = data_link_type.lower()
|
||||
@ -1442,7 +1469,7 @@ class Router(BaseNode):
|
||||
raise DynamipsError('Slot {slot_number} does not exist on router "{name}"'.format(name=self._name,
|
||||
slot_number=slot_number))
|
||||
if not adapter.port_exists(port_number):
|
||||
raise DynamipsError("Port {port_number} does not exist in adapter {adapter}".format(adapter=adapter,
|
||||
raise DynamipsError("Port {port_number} does not exist on adapter {adapter}".format(adapter=adapter,
|
||||
port_number=port_number))
|
||||
|
||||
nio = adapter.get_nio(port_number)
|
||||
|
@ -840,7 +840,7 @@ class IOUVM(BaseNode):
|
||||
|
||||
async def adapter_add_nio_binding(self, adapter_number, port_number, nio):
|
||||
"""
|
||||
Adds a adapter NIO binding.
|
||||
Adds an adapter NIO binding.
|
||||
|
||||
:param adapter_number: adapter number
|
||||
:param port_number: port number
|
||||
@ -854,7 +854,7 @@ class IOUVM(BaseNode):
|
||||
adapter_number=adapter_number))
|
||||
|
||||
if not adapter.port_exists(port_number):
|
||||
raise IOUError("Port {port_number} does not exist in adapter {adapter}".format(adapter=adapter,
|
||||
raise IOUError("Port {port_number} does not exist on adapter {adapter}".format(adapter=adapter,
|
||||
port_number=port_number))
|
||||
|
||||
adapter.add_nio(port_number, nio)
|
||||
@ -877,7 +877,7 @@ class IOUVM(BaseNode):
|
||||
|
||||
async def adapter_update_nio_binding(self, adapter_number, port_number, nio):
|
||||
"""
|
||||
Update a port NIO binding.
|
||||
Updates an adapter NIO binding.
|
||||
|
||||
:param adapter_number: adapter number
|
||||
:param port_number: port number
|
||||
@ -913,6 +913,7 @@ class IOUVM(BaseNode):
|
||||
|
||||
:param adapter_number: adapter number
|
||||
:param port_number: port number
|
||||
|
||||
:returns: NIO instance
|
||||
"""
|
||||
|
||||
@ -923,7 +924,7 @@ class IOUVM(BaseNode):
|
||||
adapter_number=adapter_number))
|
||||
|
||||
if not adapter.port_exists(port_number):
|
||||
raise IOUError("Port {port_number} does not exist in adapter {adapter}".format(adapter=adapter,
|
||||
raise IOUError("Port {port_number} does not exist on adapter {adapter}".format(adapter=adapter,
|
||||
port_number=port_number))
|
||||
|
||||
nio = adapter.get_nio(port_number)
|
||||
@ -944,6 +945,33 @@ class IOUVM(BaseNode):
|
||||
|
||||
return nio
|
||||
|
||||
def get_nio(self, adapter_number, port_number):
|
||||
"""
|
||||
Gets an adapter NIO binding.
|
||||
|
||||
:param adapter_number: adapter number
|
||||
:param port_number: port number
|
||||
|
||||
:returns: NIO instance
|
||||
"""
|
||||
|
||||
try:
|
||||
adapter = self._adapters[adapter_number]
|
||||
except IndexError:
|
||||
raise IOUError('Adapter {adapter_number} does not exist on IOU "{name}"'.format(name=self._name,
|
||||
adapter_number=adapter_number))
|
||||
|
||||
if not adapter.port_exists(port_number):
|
||||
raise IOUError("Port {port_number} does not exist on adapter {adapter}".format(adapter=adapter,
|
||||
port_number=port_number))
|
||||
|
||||
nio = adapter.get_nio(port_number)
|
||||
|
||||
if not nio:
|
||||
raise IOUError("NIO {port_number} does not exist on adapter {adapter}".format(adapter=adapter,
|
||||
port_number=port_number))
|
||||
return nio
|
||||
|
||||
@property
|
||||
def l1_keepalives(self):
|
||||
"""
|
||||
@ -1221,21 +1249,7 @@ class IOUVM(BaseNode):
|
||||
:param data_link_type: PCAP data link type (DLT_*), default is DLT_EN10MB
|
||||
"""
|
||||
|
||||
try:
|
||||
adapter = self._adapters[adapter_number]
|
||||
except IndexError:
|
||||
raise IOUError('Adapter {adapter_number} does not exist on IOU "{name}"'.format(name=self._name,
|
||||
adapter_number=adapter_number))
|
||||
|
||||
if not adapter.port_exists(port_number):
|
||||
raise IOUError("Port {port_number} does not exist in adapter {adapter}".format(adapter=adapter,
|
||||
port_number=port_number))
|
||||
|
||||
nio = adapter.get_nio(port_number)
|
||||
if not nio:
|
||||
raise IOUError("NIO {port_number} does not exist in adapter {adapter}".format(adapter=adapter,
|
||||
port_number=port_number))
|
||||
|
||||
nio = self.get_nio(adapter_number, port_number)
|
||||
if nio.capturing:
|
||||
raise IOUError("Packet capture is already activated on {adapter_number}/{port_number}".format(adapter_number=adapter_number,
|
||||
port_number=port_number))
|
||||
@ -1263,21 +1277,7 @@ class IOUVM(BaseNode):
|
||||
:param port_number: port number
|
||||
"""
|
||||
|
||||
try:
|
||||
adapter = self._adapters[adapter_number]
|
||||
except IndexError:
|
||||
raise IOUError('Adapter {adapter_number} does not exist on IOU "{name}"'.format(name=self._name,
|
||||
adapter_number=adapter_number))
|
||||
|
||||
if not adapter.port_exists(port_number):
|
||||
raise IOUError("Port {port_number} does not exist in adapter {adapter}".format(adapter=adapter,
|
||||
port_number=port_number))
|
||||
|
||||
nio = adapter.get_nio(port_number)
|
||||
if not nio:
|
||||
raise IOUError("NIO {port_number} does not exist in adapter {adapter}".format(adapter=adapter,
|
||||
port_number=port_number))
|
||||
|
||||
nio = self.get_nio(adapter_number, port_number)
|
||||
nio.stopPacketCapture()
|
||||
log.info('IOU "{name}" [{id}]: stopping packet capture on {adapter_number}/{port_number}'.format(name=self._name,
|
||||
id=self._id,
|
||||
|
@ -239,12 +239,12 @@ class Project:
|
||||
|
||||
def capture_working_directory(self):
|
||||
"""
|
||||
Returns a working directory where to temporary store packet capture files.
|
||||
Returns a working directory where to store packet capture files.
|
||||
|
||||
:returns: path to the directory
|
||||
"""
|
||||
|
||||
workdir = os.path.join(self._path, "tmp", "captures")
|
||||
workdir = os.path.join(self._path, "project-files", "captures")
|
||||
if not self._deleted:
|
||||
try:
|
||||
os.makedirs(workdir, exist_ok=True)
|
||||
|
@ -1209,7 +1209,7 @@ class QemuVM(BaseNode):
|
||||
|
||||
async def adapter_add_nio_binding(self, adapter_number, nio):
|
||||
"""
|
||||
Adds a port NIO binding.
|
||||
Adds an adapter NIO binding.
|
||||
|
||||
:param adapter_number: adapter number
|
||||
:param nio: NIO instance to add to the adapter
|
||||
@ -1239,10 +1239,10 @@ class QemuVM(BaseNode):
|
||||
|
||||
async def adapter_update_nio_binding(self, adapter_number, nio):
|
||||
"""
|
||||
Update a port NIO binding.
|
||||
Update an adapter NIO binding.
|
||||
|
||||
:param adapter_number: adapter number
|
||||
:param nio: NIO instance to add to the adapter
|
||||
:param nio: NIO instance to update the adapter
|
||||
"""
|
||||
|
||||
if self.is_running():
|
||||
@ -1260,7 +1260,7 @@ class QemuVM(BaseNode):
|
||||
|
||||
async def adapter_remove_nio_binding(self, adapter_number):
|
||||
"""
|
||||
Removes a port NIO binding.
|
||||
Removes an adapter NIO binding.
|
||||
|
||||
:param adapter_number: adapter number
|
||||
|
||||
@ -1288,12 +1288,13 @@ class QemuVM(BaseNode):
|
||||
adapter_number=adapter_number))
|
||||
return nio
|
||||
|
||||
async def start_capture(self, adapter_number, output_file):
|
||||
def get_nio(self, adapter_number):
|
||||
"""
|
||||
Starts a packet capture.
|
||||
Gets an adapter NIO binding.
|
||||
|
||||
:param adapter_number: adapter number
|
||||
:param output_file: PCAP destination file for the capture
|
||||
|
||||
:returns: NIO instance
|
||||
"""
|
||||
|
||||
try:
|
||||
@ -1307,6 +1308,17 @@ class QemuVM(BaseNode):
|
||||
if not nio:
|
||||
raise QemuError("Adapter {} is not connected".format(adapter_number))
|
||||
|
||||
return nio
|
||||
|
||||
async def start_capture(self, adapter_number, output_file):
|
||||
"""
|
||||
Starts a packet capture.
|
||||
|
||||
:param adapter_number: adapter number
|
||||
:param output_file: PCAP destination file for the capture
|
||||
"""
|
||||
|
||||
nio = self.get_nio(adapter_number)
|
||||
if nio.capturing:
|
||||
raise QemuError("Packet capture is already activated on adapter {adapter_number}".format(adapter_number=adapter_number))
|
||||
|
||||
@ -1327,16 +1339,7 @@ class QemuVM(BaseNode):
|
||||
:param adapter_number: adapter number
|
||||
"""
|
||||
|
||||
try:
|
||||
adapter = self._ethernet_adapters[adapter_number]
|
||||
except IndexError:
|
||||
raise QemuError('Adapter {adapter_number} does not exist on QEMU VM "{name}"'.format(name=self._name,
|
||||
adapter_number=adapter_number))
|
||||
nio = adapter.get_nio(0)
|
||||
|
||||
if not nio:
|
||||
raise QemuError("Adapter {} is not connected".format(adapter_number))
|
||||
|
||||
nio = self.get_nio(adapter_number)
|
||||
nio.stopPacketCapture()
|
||||
|
||||
if self.ubridge:
|
||||
|
@ -322,8 +322,15 @@ class TraceNGVM(BaseNode):
|
||||
return nio
|
||||
|
||||
async def port_update_nio_binding(self, port_number, nio):
|
||||
"""
|
||||
Updates a port NIO binding.
|
||||
|
||||
:param port_number: port number
|
||||
:param nio: NIO instance to update on the slot/port
|
||||
"""
|
||||
|
||||
if not self._ethernet_adapter.port_exists(port_number):
|
||||
raise TraceNGError("Port {port_number} doesn't exist in adapter {adapter}".format(adapter=self._ethernet_adapter,
|
||||
raise TraceNGError("Port {port_number} doesn't exist on adapter {adapter}".format(adapter=self._ethernet_adapter,
|
||||
port_number=port_number))
|
||||
if self.is_running():
|
||||
await self.update_ubridge_udp_connection("TraceNG-{}".format(self._id), self._local_udp_tunnel[1], nio)
|
||||
@ -355,6 +362,23 @@ class TraceNGVM(BaseNode):
|
||||
port_number=port_number))
|
||||
return nio
|
||||
|
||||
def get_nio(self, port_number):
|
||||
"""
|
||||
Gets a port NIO binding.
|
||||
|
||||
:param port_number: port number
|
||||
|
||||
:returns: NIO instance
|
||||
"""
|
||||
|
||||
if not self._ethernet_adapter.port_exists(port_number):
|
||||
raise TraceNGError("Port {port_number} doesn't exist on adapter {adapter}".format(adapter=self._ethernet_adapter,
|
||||
port_number=port_number))
|
||||
nio = self._ethernet_adapter.get_nio(port_number)
|
||||
if not nio:
|
||||
raise TraceNGError("Port {} is not connected".format(port_number))
|
||||
return nio
|
||||
|
||||
async def start_capture(self, port_number, output_file):
|
||||
"""
|
||||
Starts a packet capture.
|
||||
@ -363,15 +387,7 @@ class TraceNGVM(BaseNode):
|
||||
:param output_file: PCAP destination file for the capture
|
||||
"""
|
||||
|
||||
if not self._ethernet_adapter.port_exists(port_number):
|
||||
raise TraceNGError("Port {port_number} doesn't exist in adapter {adapter}".format(adapter=self._ethernet_adapter,
|
||||
port_number=port_number))
|
||||
|
||||
nio = self._ethernet_adapter.get_nio(0)
|
||||
|
||||
if not nio:
|
||||
raise TraceNGError("Port {} is not connected".format(port_number))
|
||||
|
||||
nio = self.get_nio(port_number)
|
||||
if nio.capturing:
|
||||
raise TraceNGError("Packet capture is already activated on port {port_number}".format(port_number=port_number))
|
||||
|
||||
@ -392,15 +408,7 @@ class TraceNGVM(BaseNode):
|
||||
:param port_number: port number
|
||||
"""
|
||||
|
||||
if not self._ethernet_adapter.port_exists(port_number):
|
||||
raise TraceNGError("Port {port_number} doesn't exist in adapter {adapter}".format(adapter=self._ethernet_adapter,
|
||||
port_number=port_number))
|
||||
|
||||
nio = self._ethernet_adapter.get_nio(0)
|
||||
|
||||
if not nio:
|
||||
raise TraceNGError("Port {} is not connected".format(port_number))
|
||||
|
||||
nio = self.get_nio(port_number)
|
||||
nio.stopPacketCapture()
|
||||
|
||||
if self.ubridge:
|
||||
|
@ -1014,10 +1014,10 @@ class VirtualBoxVM(BaseNode):
|
||||
|
||||
async def adapter_update_nio_binding(self, adapter_number, nio):
|
||||
"""
|
||||
Update a port NIO binding.
|
||||
Update an adapter NIO binding.
|
||||
|
||||
:param adapter_number: adapter number
|
||||
:param nio: NIO instance to add to the adapter
|
||||
:param nio: NIO instance to update on the adapter
|
||||
"""
|
||||
|
||||
if self.is_running():
|
||||
@ -1030,10 +1030,8 @@ class VirtualBoxVM(BaseNode):
|
||||
else:
|
||||
await self._control_vm("setlinkstate{} on".format(adapter_number + 1))
|
||||
except IndexError:
|
||||
raise VirtualBoxError('Adapter {adapter_number} does not exist on VirtualBox VM "{name}"'.format(
|
||||
name=self._name,
|
||||
adapter_number=adapter_number
|
||||
))
|
||||
raise VirtualBoxError('Adapter {adapter_number} does not exist on VirtualBox VM "{name}"'.format(name=self._name,
|
||||
adapter_number=adapter_number))
|
||||
|
||||
async def adapter_remove_nio_binding(self, adapter_number):
|
||||
"""
|
||||
@ -1067,6 +1065,28 @@ class VirtualBoxVM(BaseNode):
|
||||
adapter_number=adapter_number))
|
||||
return nio
|
||||
|
||||
def get_nio(self, adapter_number):
|
||||
"""
|
||||
Gets an adapter NIO binding.
|
||||
|
||||
:param adapter_number: adapter number
|
||||
|
||||
:returns: NIO instance
|
||||
"""
|
||||
|
||||
try:
|
||||
adapter = self.ethernet_adapters[adapter_number]
|
||||
except KeyError:
|
||||
raise VirtualBoxError("Adapter {adapter_number} doesn't exist on VirtualBox VM '{name}'".format(name=self.name,
|
||||
adapter_number=adapter_number))
|
||||
|
||||
nio = adapter.get_nio(0)
|
||||
|
||||
if not nio:
|
||||
raise VirtualBoxError("Adapter {} is not connected".format(adapter_number))
|
||||
|
||||
return nio
|
||||
|
||||
def is_running(self):
|
||||
"""
|
||||
:returns: True if the vm is not stopped
|
||||
@ -1081,17 +1101,7 @@ class VirtualBoxVM(BaseNode):
|
||||
:param output_file: PCAP destination file for the capture
|
||||
"""
|
||||
|
||||
try:
|
||||
adapter = self._ethernet_adapters[adapter_number]
|
||||
except KeyError:
|
||||
raise VirtualBoxError("Adapter {adapter_number} doesn't exist on VirtualBox VM '{name}'".format(name=self.name,
|
||||
adapter_number=adapter_number))
|
||||
|
||||
nio = adapter.get_nio(0)
|
||||
|
||||
if not nio:
|
||||
raise VirtualBoxError("Adapter {} is not connected".format(adapter_number))
|
||||
|
||||
nio = self.get_nio(adapter_number)
|
||||
if nio.capturing:
|
||||
raise VirtualBoxError("Packet capture is already activated on adapter {adapter_number}".format(adapter_number=adapter_number))
|
||||
|
||||
@ -1112,17 +1122,7 @@ class VirtualBoxVM(BaseNode):
|
||||
:param adapter_number: adapter number
|
||||
"""
|
||||
|
||||
try:
|
||||
adapter = self._ethernet_adapters[adapter_number]
|
||||
except KeyError:
|
||||
raise VirtualBoxError("Adapter {adapter_number} doesn't exist on VirtualBox VM '{name}'".format(name=self.name,
|
||||
adapter_number=adapter_number))
|
||||
|
||||
nio = adapter.get_nio(0)
|
||||
|
||||
if not nio:
|
||||
raise VirtualBoxError("Adapter {} is not connected".format(adapter_number))
|
||||
|
||||
nio = self.get_nio(adapter_number)
|
||||
nio.stopPacketCapture()
|
||||
|
||||
if self.ubridge:
|
||||
|
@ -750,20 +750,18 @@ class VMwareVM(BaseNode):
|
||||
|
||||
async def adapter_update_nio_binding(self, adapter_number, nio):
|
||||
"""
|
||||
Update a port NIO binding.
|
||||
Updates an adapter NIO binding.
|
||||
|
||||
:param adapter_number: adapter number
|
||||
:param nio: NIO instance to add to the adapter
|
||||
:param nio: NIO instance to update on the adapter
|
||||
"""
|
||||
|
||||
if self._ubridge_hypervisor:
|
||||
try:
|
||||
await self._update_ubridge_connection(adapter_number, nio)
|
||||
except IndexError:
|
||||
raise VMwareError('Adapter {adapter_number} does not exist on VMware VM "{name}"'.format(
|
||||
name=self._name,
|
||||
adapter_number=adapter_number
|
||||
))
|
||||
raise VMwareError('Adapter {adapter_number} does not exist on VMware VM "{name}"'.format(name=self._name,
|
||||
adapter_number=adapter_number))
|
||||
|
||||
async def adapter_remove_nio_binding(self, adapter_number):
|
||||
"""
|
||||
@ -794,6 +792,27 @@ class VMwareVM(BaseNode):
|
||||
|
||||
return nio
|
||||
|
||||
def get_nio(self, adapter_number):
|
||||
"""
|
||||
Gets an adapter NIO binding.
|
||||
|
||||
:param adapter_number: adapter number
|
||||
|
||||
:returns: NIO instance
|
||||
"""
|
||||
|
||||
try:
|
||||
adapter = self.ethernet_adapters[adapter_number]
|
||||
except KeyError:
|
||||
raise VMwareError("Adapter {adapter_number} doesn't exist on VMware VM '{name}'".format(name=self.name,
|
||||
adapter_number=adapter_number))
|
||||
|
||||
nio = adapter.get_nio(0)
|
||||
if not nio:
|
||||
raise VMwareError("Adapter {} is not connected".format(adapter_number))
|
||||
|
||||
return nio
|
||||
|
||||
def _get_pipe_name(self):
|
||||
"""
|
||||
Returns the pipe name to create a serial connection.
|
||||
@ -875,17 +894,7 @@ class VMwareVM(BaseNode):
|
||||
:param output_file: PCAP destination file for the capture
|
||||
"""
|
||||
|
||||
try:
|
||||
adapter = self._ethernet_adapters[adapter_number]
|
||||
except KeyError:
|
||||
raise VMwareError("Adapter {adapter_number} doesn't exist on VMware VM '{name}'".format(name=self.name,
|
||||
adapter_number=adapter_number))
|
||||
|
||||
nio = adapter.get_nio(0)
|
||||
|
||||
if not nio:
|
||||
raise VMwareError("Adapter {} is not connected".format(adapter_number))
|
||||
|
||||
nio = self.get_nio(adapter_number)
|
||||
if nio.capturing:
|
||||
raise VMwareError("Packet capture is already activated on adapter {adapter_number}".format(adapter_number=adapter_number))
|
||||
|
||||
@ -905,17 +914,7 @@ class VMwareVM(BaseNode):
|
||||
:param adapter_number: adapter number
|
||||
"""
|
||||
|
||||
try:
|
||||
adapter = self._ethernet_adapters[adapter_number]
|
||||
except KeyError:
|
||||
raise VMwareError("Adapter {adapter_number} doesn't exist on VMware VM '{name}'".format(name=self.name,
|
||||
adapter_number=adapter_number))
|
||||
|
||||
nio = adapter.get_nio(0)
|
||||
|
||||
if not nio:
|
||||
raise VMwareError("Adapter {} is not connected".format(adapter_number))
|
||||
|
||||
nio = self.get_nio(adapter_number)
|
||||
nio.stopPacketCapture()
|
||||
|
||||
if self._started:
|
||||
|
@ -367,7 +367,7 @@ class VPCSVM(BaseNode):
|
||||
"""
|
||||
|
||||
if not self._ethernet_adapter.port_exists(port_number):
|
||||
raise VPCSError("Port {port_number} doesn't exist in adapter {adapter}".format(adapter=self._ethernet_adapter,
|
||||
raise VPCSError("Port {port_number} doesn't exist on adapter {adapter}".format(adapter=self._ethernet_adapter,
|
||||
port_number=port_number))
|
||||
|
||||
if self.is_running():
|
||||
@ -382,8 +382,15 @@ class VPCSVM(BaseNode):
|
||||
return nio
|
||||
|
||||
async def port_update_nio_binding(self, port_number, nio):
|
||||
"""
|
||||
Updates a port NIO binding.
|
||||
|
||||
:param port_number: port number
|
||||
:param nio: NIO instance to update on the slot/port
|
||||
"""
|
||||
|
||||
if not self._ethernet_adapter.port_exists(port_number):
|
||||
raise VPCSError("Port {port_number} doesn't exist in adapter {adapter}".format(adapter=self._ethernet_adapter,
|
||||
raise VPCSError("Port {port_number} doesn't exist on adapter {adapter}".format(adapter=self._ethernet_adapter,
|
||||
port_number=port_number))
|
||||
if self.is_running():
|
||||
await self.update_ubridge_udp_connection("VPCS-{}".format(self._id), self._local_udp_tunnel[1], nio)
|
||||
@ -398,7 +405,7 @@ class VPCSVM(BaseNode):
|
||||
"""
|
||||
|
||||
if not self._ethernet_adapter.port_exists(port_number):
|
||||
raise VPCSError("Port {port_number} doesn't exist in adapter {adapter}".format(adapter=self._ethernet_adapter,
|
||||
raise VPCSError("Port {port_number} doesn't exist on adapter {adapter}".format(adapter=self._ethernet_adapter,
|
||||
port_number=port_number))
|
||||
|
||||
if self.is_running():
|
||||
@ -415,6 +422,23 @@ class VPCSVM(BaseNode):
|
||||
port_number=port_number))
|
||||
return nio
|
||||
|
||||
def get_nio(self, port_number):
|
||||
"""
|
||||
Gets a port NIO binding.
|
||||
|
||||
:param port_number: port number
|
||||
|
||||
:returns: NIO instance
|
||||
"""
|
||||
|
||||
if not self._ethernet_adapter.port_exists(port_number):
|
||||
raise VPCSError("Port {port_number} doesn't exist on adapter {adapter}".format(adapter=self._ethernet_adapter,
|
||||
port_number=port_number))
|
||||
nio = self._ethernet_adapter.get_nio(port_number)
|
||||
if not nio:
|
||||
raise VPCSError("Port {} is not connected".format(port_number))
|
||||
return nio
|
||||
|
||||
async def start_capture(self, port_number, output_file):
|
||||
"""
|
||||
Starts a packet capture.
|
||||
@ -423,17 +447,9 @@ class VPCSVM(BaseNode):
|
||||
:param output_file: PCAP destination file for the capture
|
||||
"""
|
||||
|
||||
if not self._ethernet_adapter.port_exists(port_number):
|
||||
raise VPCSError("Port {port_number} doesn't exist in adapter {adapter}".format(adapter=self._ethernet_adapter,
|
||||
port_number=port_number))
|
||||
|
||||
nio = self._ethernet_adapter.get_nio(0)
|
||||
|
||||
if not nio:
|
||||
raise VPCSError("Port {} is not connected".format(port_number))
|
||||
|
||||
nio = self.get_nio(port_number)
|
||||
if nio.capturing:
|
||||
raise VPCSError("Packet capture is already activated on port {port_number}".format(port_number=port_number))
|
||||
raise VPCSError("Packet capture is already active on port {port_number}".format(port_number=port_number))
|
||||
|
||||
nio.startPacketCapture(output_file)
|
||||
|
||||
@ -452,15 +468,7 @@ class VPCSVM(BaseNode):
|
||||
:param port_number: port number
|
||||
"""
|
||||
|
||||
if not self._ethernet_adapter.port_exists(port_number):
|
||||
raise VPCSError("Port {port_number} doesn't exist in adapter {adapter}".format(adapter=self._ethernet_adapter,
|
||||
port_number=port_number))
|
||||
|
||||
nio = self._ethernet_adapter.get_nio(0)
|
||||
|
||||
if not nio:
|
||||
raise VPCSError("Port {} is not connected".format(port_number))
|
||||
|
||||
nio = self.get_nio(port_number)
|
||||
nio.stopPacketCapture()
|
||||
|
||||
if self.ubridge:
|
||||
|
@ -330,31 +330,17 @@ class Compute:
|
||||
:returns: A file stream
|
||||
"""
|
||||
|
||||
# Due to Python 3.4 limitation we can't use with and asyncio
|
||||
# https://www.python.org/dev/peps/pep-0492/
|
||||
# that why we wrap the answer
|
||||
class StreamResponse:
|
||||
|
||||
def __init__(self, response):
|
||||
self._response = response
|
||||
|
||||
def __enter__(self):
|
||||
return self._response.content
|
||||
|
||||
def __exit__(self):
|
||||
self._response.close()
|
||||
|
||||
url = self._getUrl("/projects/{}/stream/{}".format(project.id, path))
|
||||
response = await self._session().request("GET", url, auth=self._auth, timeout=timeout)
|
||||
if response.status == 404:
|
||||
raise aiohttp.web.HTTPNotFound(text="{} not found on compute".format(path))
|
||||
raise aiohttp.web.HTTPNotFound(text="file '{}' not found on compute".format(path))
|
||||
elif response.status == 403:
|
||||
raise aiohttp.web.HTTPForbidden(text="forbidden to open {} on compute".format(path))
|
||||
raise aiohttp.web.HTTPForbidden(text="forbidden to open '{}' on compute".format(path))
|
||||
elif response.status != 200:
|
||||
raise aiohttp.web.HTTPInternalServerError(text="Unexpected error {}: {}: while opening {} on compute".format(response.status,
|
||||
response.reason,
|
||||
path))
|
||||
return StreamResponse(response)
|
||||
return response
|
||||
|
||||
async def http_query(self, method, path, data=None, dont_connect=False, **kwargs):
|
||||
"""
|
||||
|
@ -19,7 +19,6 @@ import os
|
||||
import re
|
||||
import uuid
|
||||
import html
|
||||
import asyncio
|
||||
import aiohttp
|
||||
|
||||
import logging
|
||||
@ -118,6 +117,7 @@ class Link:
|
||||
self._nodes = []
|
||||
self._project = project
|
||||
self._capturing = False
|
||||
self._capture_node = None
|
||||
self._capture_file_name = None
|
||||
self._streaming_pcap = None
|
||||
self._created = False
|
||||
@ -139,6 +139,34 @@ class Link:
|
||||
"""
|
||||
return self._nodes
|
||||
|
||||
@property
|
||||
def project(self):
|
||||
"""
|
||||
Get the project this link belongs to.
|
||||
|
||||
:return: Project instance.
|
||||
"""
|
||||
return self._project
|
||||
|
||||
@property
|
||||
def capture_node(self):
|
||||
"""
|
||||
Get the capturing node
|
||||
|
||||
:return: Node instance.
|
||||
"""
|
||||
return self._capture_node
|
||||
|
||||
@property
|
||||
def compute(self):
|
||||
"""
|
||||
Get the capturing node
|
||||
|
||||
:return: Node instance.
|
||||
"""
|
||||
assert self.capture_node
|
||||
return self.capture_node["node"].compute
|
||||
|
||||
def get_active_filters(self):
|
||||
"""
|
||||
Return the active filters.
|
||||
@ -289,44 +317,8 @@ class Link:
|
||||
|
||||
self._capturing = True
|
||||
self._capture_file_name = capture_file_name
|
||||
self._streaming_pcap = asyncio.ensure_future(self._start_streaming_pcap())
|
||||
self._project.controller.notification.project_emit("link.updated", self.__json__())
|
||||
|
||||
async def _start_streaming_pcap(self):
|
||||
"""
|
||||
Dump a pcap file on disk
|
||||
"""
|
||||
|
||||
if os.path.exists(self.capture_file_path):
|
||||
try:
|
||||
os.remove(self.capture_file_path)
|
||||
except OSError as e:
|
||||
raise aiohttp.web.HTTPConflict(text="Could not delete old capture file '{}': {}".format(self.capture_file_path, e))
|
||||
|
||||
try:
|
||||
stream_content = await self.read_pcap_from_source()
|
||||
except aiohttp.web.HTTPException as e:
|
||||
error_msg = "Could not stream PCAP file: error {}: {}".format(e.status, e.text)
|
||||
log.error(error_msg)
|
||||
self._capturing = False
|
||||
self._project.notification.project_emit("log.error", {"message": error_msg})
|
||||
self._project.controller.notification.project_emit("link.updated", self.__json__())
|
||||
|
||||
with stream_content as stream:
|
||||
try:
|
||||
with open(self.capture_file_path, "wb") as f:
|
||||
while self._capturing:
|
||||
# We read 1 bytes by 1 otherwise the remaining data is not read if the traffic stops
|
||||
data = await stream.read(1)
|
||||
if data:
|
||||
f.write(data)
|
||||
# Flush to disk otherwise the live is not really live
|
||||
f.flush()
|
||||
else:
|
||||
break
|
||||
except OSError as e:
|
||||
raise aiohttp.web.HTTPConflict(text="Could not write capture file '{}': {}".format(self.capture_file_path, e))
|
||||
|
||||
async def stop_capture(self):
|
||||
"""
|
||||
Stop capture on the link
|
||||
@ -335,12 +327,26 @@ class Link:
|
||||
self._capturing = False
|
||||
self._project.controller.notification.project_emit("link.updated", self.__json__())
|
||||
|
||||
async def _read_pcap_from_source(self):
|
||||
def pcap_streaming_url(self):
|
||||
"""
|
||||
Return a FileStream of the Pcap from the compute server
|
||||
Get the PCAP streaming URL on compute
|
||||
|
||||
:returns: URL
|
||||
"""
|
||||
|
||||
raise NotImplementedError
|
||||
assert self.capture_node
|
||||
compute = self.capture_node["node"].compute
|
||||
node_type = self.capture_node["node"].node_type
|
||||
node_id = self.capture_node["node"].id
|
||||
adapter_number = self.capture_node["adapter_number"]
|
||||
port_number = self.capture_node["port_number"]
|
||||
url = "/projects/{project_id}/{node_type}/nodes/{node_id}/adapters/{adapter_number}/ports/{port_number}/pcap".format(project_id=self.project.id,
|
||||
node_type=node_type,
|
||||
node_id=node_id,
|
||||
adapter_number=adapter_number,
|
||||
port_number=port_number)
|
||||
|
||||
return compute._getUrl(url)
|
||||
|
||||
async def node_updated(self, node):
|
||||
"""
|
||||
|
@ -15,7 +15,7 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import asyncio
|
||||
|
||||
import aiohttp
|
||||
|
||||
|
||||
@ -26,7 +26,6 @@ class UDPLink(Link):
|
||||
|
||||
def __init__(self, project, link_id=None):
|
||||
super().__init__(project, link_id=link_id)
|
||||
self._capture_node = None
|
||||
self._created = False
|
||||
self._link_data = []
|
||||
|
||||
@ -164,10 +163,8 @@ class UDPLink(Link):
|
||||
if not capture_file_name:
|
||||
capture_file_name = self.default_capture_file_name()
|
||||
self._capture_node = self._choose_capture_side()
|
||||
data = {
|
||||
"capture_file_name": capture_file_name,
|
||||
"data_link_type": data_link_type
|
||||
}
|
||||
data = {"capture_file_name": capture_file_name,
|
||||
"data_link_type": data_link_type}
|
||||
await self._capture_node["node"].post("/adapters/{adapter_number}/ports/{port_number}/start_capture".format(adapter_number=self._capture_node["adapter_number"], port_number=self._capture_node["port_number"]), data=data)
|
||||
await super().start_capture(data_link_type=data_link_type, capture_file_name=capture_file_name)
|
||||
|
||||
@ -210,14 +207,6 @@ class UDPLink(Link):
|
||||
|
||||
raise aiohttp.web.HTTPConflict(text="Cannot capture because there is no running device on this link")
|
||||
|
||||
async def read_pcap_from_source(self):
|
||||
"""
|
||||
Return a FileStream of the Pcap from the compute node
|
||||
"""
|
||||
if self._capture_node:
|
||||
compute = self._capture_node["node"].compute
|
||||
return compute.stream_file(self._project, "tmp/captures/" + self._capture_file_name)
|
||||
|
||||
async def node_updated(self, node):
|
||||
"""
|
||||
Called when a node member of the link is updated
|
||||
|
@ -288,3 +288,25 @@ class ATMSwitchHandler:
|
||||
port_number = int(request.match_info["port_number"])
|
||||
await node.stop_capture(port_number)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/atm_relay_switch/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/pcap",
|
||||
description="Stream the pcap capture file",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Adapter to steam a packet capture (always 0)",
|
||||
"port_number": "Port on the switch"
|
||||
},
|
||||
status_codes={
|
||||
200: "File returned",
|
||||
403: "Permission denied",
|
||||
404: "The file doesn't exist"
|
||||
})
|
||||
async def stream_pcap_file(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = dynamips_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
port_number = int(request.match_info["port_number"])
|
||||
nio = node.get_nio(port_number)
|
||||
await node.stream_pcap_file(nio, node.project.id, request, response)
|
||||
|
@ -222,21 +222,16 @@ class CloudHandler:
|
||||
},
|
||||
input=NIO_SCHEMA,
|
||||
output=NIO_SCHEMA,
|
||||
description="Update a NIO from a Cloud instance")
|
||||
description="Update a NIO on a Cloud instance")
|
||||
async def update_nio(request, response):
|
||||
|
||||
builtin_manager = Builtin.instance()
|
||||
node = builtin_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
|
||||
try:
|
||||
nio = node.nios[adapter_number]
|
||||
except KeyError:
|
||||
raise HTTPConflict(text="NIO `{}` doesn't exist".format(adapter_number))
|
||||
|
||||
if "filters" in request.json and nio:
|
||||
port_number = int(request.match_info["port_number"])
|
||||
nio = node.get_nio(port_number)
|
||||
if "filters" in request.json:
|
||||
nio.filters = request.json["filters"]
|
||||
await node.update_nio(int(request.match_info["port_number"]), nio)
|
||||
await node.update_nio(port_number, nio)
|
||||
response.set_status(201)
|
||||
response.json(request.json)
|
||||
|
||||
@ -307,3 +302,25 @@ class CloudHandler:
|
||||
port_number = int(request.match_info["port_number"])
|
||||
await node.stop_capture(port_number)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/cloud/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/pcap",
|
||||
description="Stream the pcap capture file",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Adapter to steam a packet capture (always 0)",
|
||||
"port_number": "Port on the cloud"
|
||||
},
|
||||
status_codes={
|
||||
200: "File returned",
|
||||
403: "Permission denied",
|
||||
404: "The file doesn't exist"
|
||||
})
|
||||
async def stream_pcap_file(request, response):
|
||||
|
||||
builtin_manager = Builtin.instance()
|
||||
node = builtin_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
port_number = int(request.match_info["port_number"])
|
||||
nio = node.get_nio(port_number)
|
||||
await builtin_manager.stream_pcap_file(nio, node.project.id, request, response)
|
||||
|
@ -238,8 +238,9 @@ class DockerHandler:
|
||||
nio_type = request.json["type"]
|
||||
if nio_type != "nio_udp":
|
||||
raise HTTPConflict(text="NIO of type {} is not supported".format(nio_type))
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
nio = docker_manager.create_nio(request.json)
|
||||
await container.adapter_add_nio_binding(int(request.match_info["adapter_number"]), nio)
|
||||
await container.adapter_add_nio_binding(adapter_number, nio)
|
||||
response.set_status(201)
|
||||
response.json(nio)
|
||||
|
||||
@ -249,7 +250,7 @@ class DockerHandler:
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Network adapter where the nio is located",
|
||||
"port_number": "Port from where the nio should be updated"
|
||||
"port_number": "Port from where the nio should be updated (always 0)"
|
||||
},
|
||||
status_codes={
|
||||
201: "NIO updated",
|
||||
@ -258,15 +259,16 @@ class DockerHandler:
|
||||
},
|
||||
input=NIO_SCHEMA,
|
||||
output=NIO_SCHEMA,
|
||||
description="Update a NIO from a Docker instance")
|
||||
description="Update a NIO on a Docker instance")
|
||||
async def update_nio(request, response):
|
||||
|
||||
docker_manager = Docker.instance()
|
||||
container = docker_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
nio = container.ethernet_adapters[int(request.match_info["adapter_number"])].get_nio(0)
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
nio = container.get_nio(adapter_number)
|
||||
if "filters" in request.json and nio:
|
||||
nio.filters = request.json["filters"]
|
||||
await container.adapter_update_nio_binding(int(request.match_info["port_number"]), nio)
|
||||
await container.adapter_update_nio_binding(adapter_number, nio)
|
||||
response.set_status(201)
|
||||
response.json(request.json)
|
||||
|
||||
@ -276,7 +278,7 @@ class DockerHandler:
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Adapter where the nio should be added",
|
||||
"port_number": "Port on the adapter"
|
||||
"port_number": "Port on the adapter (always 0)"
|
||||
},
|
||||
status_codes={
|
||||
204: "NIO deleted",
|
||||
@ -287,7 +289,8 @@ class DockerHandler:
|
||||
async def delete_nio(request, response):
|
||||
docker_manager = Docker.instance()
|
||||
container = docker_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
await container.adapter_remove_nio_binding(int(request.match_info["adapter_number"]))
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
await container.adapter_remove_nio_binding(adapter_number)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.put(
|
||||
@ -333,7 +336,7 @@ class DockerHandler:
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Adapter to start a packet capture",
|
||||
"port_number": "Port on the adapter"
|
||||
"port_number": "Port on the adapter (always 0)"
|
||||
},
|
||||
status_codes={
|
||||
200: "Capture started",
|
||||
@ -349,7 +352,6 @@ class DockerHandler:
|
||||
container = docker_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
pcap_file_path = os.path.join(container.project.capture_working_directory(), request.json["capture_file_name"])
|
||||
|
||||
await container.start_capture(adapter_number, pcap_file_path)
|
||||
response.json({"pcap_file_path": str(pcap_file_path)})
|
||||
|
||||
@ -372,11 +374,32 @@ class DockerHandler:
|
||||
|
||||
docker_manager = Docker.instance()
|
||||
container = docker_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
await container.stop_capture(adapter_number)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/docker/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/pcap",
|
||||
description="Stream the pcap capture file",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Adapter to steam a packet capture",
|
||||
"port_number": "Port on the adapter (always 0)"
|
||||
},
|
||||
status_codes={
|
||||
200: "File returned",
|
||||
403: "Permission denied",
|
||||
404: "The file doesn't exist"
|
||||
})
|
||||
async def stream_pcap_file(request, response):
|
||||
|
||||
docker_manager = Docker.instance()
|
||||
container = docker_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
nio = container.get_nio(adapter_number)
|
||||
await docker_manager.stream_pcap_file(nio, container.project.id, request, response)
|
||||
|
||||
@Route.get(
|
||||
r"/docker/images",
|
||||
status_codes={
|
||||
|
@ -285,15 +285,15 @@ class DynamipsVMHandler:
|
||||
},
|
||||
input=NIO_SCHEMA,
|
||||
output=NIO_SCHEMA,
|
||||
description="Update a NIO from a Dynamips instance")
|
||||
description="Update a NIO on a Dynamips instance")
|
||||
async def update_nio(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
vm = dynamips_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
slot_number = int(request.match_info["adapter_number"])
|
||||
port_number = int(request.match_info["port_number"])
|
||||
nio = vm.slots[slot_number].get_nio(port_number)
|
||||
if "filters" in request.json and nio:
|
||||
nio = vm.get_nio(slot_number, port_number)
|
||||
if "filters" in request.json:
|
||||
nio.filters = request.json["filters"]
|
||||
await vm.slot_update_nio_binding(slot_number, port_number, nio)
|
||||
response.set_status(201)
|
||||
@ -379,6 +379,29 @@ class DynamipsVMHandler:
|
||||
await vm.stop_capture(slot_number, port_number)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/dynamips/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/pcap",
|
||||
description="Stream the pcap capture file",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Adapter to steam a packet capture",
|
||||
"port_number": "Port on the adapter (always 0)"
|
||||
},
|
||||
status_codes={
|
||||
200: "File returned",
|
||||
403: "Permission denied",
|
||||
404: "The file doesn't exist"
|
||||
})
|
||||
async def stream_pcap_file(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
vm = dynamips_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
slot_number = int(request.match_info["adapter_number"])
|
||||
port_number = int(request.match_info["port_number"])
|
||||
nio = vm.get_nio(slot_number, port_number)
|
||||
await dynamips_manager.stream_pcap_file(nio, vm.project.id, request, response)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/dynamips/nodes/{node_id}/idlepc_proposals",
|
||||
parameters={
|
||||
@ -485,10 +508,8 @@ class DynamipsVMHandler:
|
||||
description="Duplicate a dynamips instance")
|
||||
async def duplicate(request, response):
|
||||
|
||||
new_node = await Dynamips.instance().duplicate_node(
|
||||
request.match_info["node_id"],
|
||||
request.json["destination_node_id"]
|
||||
)
|
||||
new_node = await Dynamips.instance().duplicate_node(request.match_info["node_id"],
|
||||
request.json["destination_node_id"])
|
||||
response.set_status(201)
|
||||
response.json(new_node)
|
||||
|
||||
|
@ -94,10 +94,8 @@ class EthernetHubHandler:
|
||||
description="Duplicate an ethernet hub instance")
|
||||
async def duplicate(request, response):
|
||||
|
||||
new_node = await Dynamips.instance().duplicate_node(
|
||||
request.match_info["node_id"],
|
||||
request.json["destination_node_id"]
|
||||
)
|
||||
new_node = await Dynamips.instance().duplicate_node(request.match_info["node_id"],
|
||||
request.json["destination_node_id"])
|
||||
response.set_status(201)
|
||||
response.json(new_node)
|
||||
|
||||
@ -292,3 +290,25 @@ class EthernetHubHandler:
|
||||
port_number = int(request.match_info["port_number"])
|
||||
await node.stop_capture(port_number)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/ethernet_hub/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/pcap",
|
||||
description="Stream the pcap capture file",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Adapter to steam a packet capture (always 0)",
|
||||
"port_number": "Port on the hub"
|
||||
},
|
||||
status_codes={
|
||||
200: "File returned",
|
||||
403: "Permission denied",
|
||||
404: "The file doesn't exist"
|
||||
})
|
||||
async def stream_pcap_file(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = dynamips_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
port_number = int(request.match_info["port_number"])
|
||||
nio = node.get_nio(port_number)
|
||||
await node.stream_pcap_file(nio, node.project.id, request, response)
|
||||
|
@ -105,10 +105,8 @@ class EthernetSwitchHandler:
|
||||
description="Duplicate an ethernet switch instance")
|
||||
async def duplicate(request, response):
|
||||
|
||||
new_node = await Dynamips.instance().duplicate_node(
|
||||
request.match_info["node_id"],
|
||||
request.json["destination_node_id"]
|
||||
)
|
||||
new_node = await Dynamips.instance().duplicate_node(request.match_info["node_id"],
|
||||
request.json["destination_node_id"])
|
||||
response.set_status(201)
|
||||
response.json(new_node)
|
||||
|
||||
@ -319,3 +317,25 @@ class EthernetSwitchHandler:
|
||||
port_number = int(request.match_info["port_number"])
|
||||
await node.stop_capture(port_number)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/ethernet_switch/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/pcap",
|
||||
description="Stream the pcap capture file",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Adapter to steam a packet capture (always 0)",
|
||||
"port_number": "Port on the switch"
|
||||
},
|
||||
status_codes={
|
||||
200: "File returned",
|
||||
403: "Permission denied",
|
||||
404: "The file doesn't exist"
|
||||
})
|
||||
async def stream_pcap_file(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = dynamips_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
port_number = int(request.match_info["port_number"])
|
||||
nio = node.get_nio(port_number)
|
||||
await node.stream_pcap_file(nio, node.project.id, request, response)
|
||||
|
@ -288,3 +288,25 @@ class FrameRelaySwitchHandler:
|
||||
port_number = int(request.match_info["port_number"])
|
||||
await node.stop_capture(port_number)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/frame_relay_switch/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/pcap",
|
||||
description="Stream the pcap capture file",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Adapter to steam a packet capture (always 0)",
|
||||
"port_number": "Port on the switch"
|
||||
},
|
||||
status_codes={
|
||||
200: "File returned",
|
||||
403: "Permission denied",
|
||||
404: "The file doesn't exist"
|
||||
})
|
||||
async def stream_pcap_file(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = dynamips_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
port_number = int(request.match_info["port_number"])
|
||||
nio = node.get_nio(port_number)
|
||||
await node.stream_pcap_file(nio, node.project.id, request, response)
|
||||
|
@ -290,7 +290,7 @@ class IOUHandler:
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Update a NIO from a IOU instance",
|
||||
description="Update a NIO on an IOU instance",
|
||||
input=NIO_SCHEMA,
|
||||
output=NIO_SCHEMA)
|
||||
async def update_nio(request, response):
|
||||
@ -299,13 +299,10 @@ class IOUHandler:
|
||||
vm = iou_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
port_number = int(request.match_info["port_number"])
|
||||
nio = vm.adapters[adapter_number].get_nio(port_number)
|
||||
if "filters" in request.json and nio:
|
||||
nio = vm.get_nio(adapter_number, port_number)
|
||||
if "filters" in request.json:
|
||||
nio.filters = request.json["filters"]
|
||||
await vm.adapter_update_nio_binding(
|
||||
adapter_number,
|
||||
port_number,
|
||||
nio)
|
||||
await vm.adapter_update_nio_binding(adapter_number, port_number, nio)
|
||||
response.set_status(201)
|
||||
response.json(nio)
|
||||
|
||||
@ -375,12 +372,34 @@ class IOUHandler:
|
||||
|
||||
iou_manager = IOU.instance()
|
||||
vm = iou_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
port_number = int(request.match_info["port_number"])
|
||||
await vm.stop_capture(adapter_number, port_number)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/iou/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/pcap",
|
||||
description="Stream the pcap capture file",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Adapter to steam a packet capture",
|
||||
"port_number": "Port on the adapter (always 0)"
|
||||
},
|
||||
status_codes={
|
||||
200: "File returned",
|
||||
403: "Permission denied",
|
||||
404: "The file doesn't exist"
|
||||
})
|
||||
async def stream_pcap_file(request, response):
|
||||
|
||||
iou_manager = IOU.instance()
|
||||
vm = iou_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
port_number = int(request.match_info["port_number"])
|
||||
nio = vm.get_nio(adapter_number, port_number)
|
||||
await iou_manager.stream_pcap_file(nio, vm.project.id, request, response)
|
||||
|
||||
@Route.get(
|
||||
r"/iou/images",
|
||||
status_codes={
|
||||
|
@ -21,6 +21,7 @@ from gns3server.web.route import Route
|
||||
from gns3server.schemas.node import NODE_CAPTURE_SCHEMA
|
||||
from gns3server.schemas.nio import NIO_SCHEMA
|
||||
from gns3server.compute.builtin import Builtin
|
||||
from aiohttp.web import HTTPConflict
|
||||
|
||||
from gns3server.schemas.nat import (
|
||||
NAT_CREATE_SCHEMA,
|
||||
@ -213,15 +214,16 @@ class NatHandler:
|
||||
},
|
||||
input=NIO_SCHEMA,
|
||||
output=NIO_SCHEMA,
|
||||
description="Update a NIO from a NAT instance")
|
||||
description="Update a NIO on a NAT instance")
|
||||
async def update_nio(request, response):
|
||||
|
||||
builtin_manager = Builtin.instance()
|
||||
node = builtin_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
nio = node.nios[int(request.match_info["adapter_number"])]
|
||||
if "filters" in request.json and nio:
|
||||
port_number = int(request.match_info["port_number"])
|
||||
nio = node.get_nio(port_number)
|
||||
if "filters" in request.json:
|
||||
nio.filters = request.json["filters"]
|
||||
await node.update_nio(int(request.match_info["port_number"]), nio)
|
||||
await node.update_nio(port_number, nio)
|
||||
response.set_status(201)
|
||||
response.json(request.json)
|
||||
|
||||
@ -292,3 +294,25 @@ class NatHandler:
|
||||
port_number = int(request.match_info["port_number"])
|
||||
await node.stop_capture(port_number)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/nat/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/pcap",
|
||||
description="Stream the pcap capture file",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Adapter to steam a packet capture (always 0)",
|
||||
"port_number": "Port on the nat"
|
||||
},
|
||||
status_codes={
|
||||
200: "File returned",
|
||||
403: "Permission denied",
|
||||
404: "The file doesn't exist"
|
||||
})
|
||||
async def stream_pcap_file(request, response):
|
||||
|
||||
builtin_manager = Builtin.instance()
|
||||
node = builtin_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
port_number = int(request.match_info["port_number"])
|
||||
nio = node.get_nio(port_number)
|
||||
await builtin_manager.stream_pcap_file(nio, node.project.id, request, response)
|
||||
|
@ -295,6 +295,7 @@ class ProjectHandler:
|
||||
response.set_status(200)
|
||||
response.enable_chunked_encoding()
|
||||
|
||||
# FIXME: file streaming is never stopped
|
||||
try:
|
||||
with open(path, "rb") as f:
|
||||
await response.prepare(request)
|
||||
@ -303,7 +304,6 @@ class ProjectHandler:
|
||||
if not data:
|
||||
await asyncio.sleep(0.1)
|
||||
await response.write(data)
|
||||
|
||||
except FileNotFoundError:
|
||||
raise aiohttp.web.HTTPNotFound()
|
||||
except PermissionError:
|
||||
|
@ -334,17 +334,18 @@ class QEMUHandler:
|
||||
},
|
||||
input=NIO_SCHEMA,
|
||||
output=NIO_SCHEMA,
|
||||
description="Update a NIO from a Qemu instance")
|
||||
description="Update a NIO on a Qemu instance")
|
||||
async def update_nio(request, response):
|
||||
|
||||
qemu_manager = Qemu.instance()
|
||||
vm = qemu_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
nio = vm.ethernet_adapters[int(request.match_info["adapter_number"])]
|
||||
if "filters" in request.json and nio:
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
nio = vm.get_nio(adapter_number)
|
||||
if "filters" in request.json:
|
||||
nio.filters = request.json["filters"]
|
||||
if "suspend" in request.json and nio:
|
||||
if "suspend" in request.json:
|
||||
nio.suspend = request.json["suspend"]
|
||||
await vm.adapter_update_nio_binding(int(request.match_info["adapter_number"]), nio)
|
||||
await vm.adapter_update_nio_binding(adapter_number, nio)
|
||||
response.set_status(201)
|
||||
response.json(request.json)
|
||||
|
||||
@ -366,7 +367,8 @@ class QEMUHandler:
|
||||
|
||||
qemu_manager = Qemu.instance()
|
||||
vm = qemu_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
await vm.adapter_remove_nio_binding(int(request.match_info["adapter_number"]))
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
await vm.adapter_remove_nio_binding(adapter_number)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
@ -415,6 +417,28 @@ class QEMUHandler:
|
||||
await vm.stop_capture(adapter_number)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/qemu/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/pcap",
|
||||
description="Stream the pcap capture file",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Adapter to steam a packet capture",
|
||||
"port_number": "Port on the adapter (always 0)"
|
||||
},
|
||||
status_codes={
|
||||
200: "File returned",
|
||||
403: "Permission denied",
|
||||
404: "The file doesn't exist"
|
||||
})
|
||||
async def stream_pcap_file(request, response):
|
||||
|
||||
qemu_manager = Qemu.instance()
|
||||
vm = qemu_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
nio = vm.get_nio(adapter_number)
|
||||
await qemu_manager.stream_pcap_file(nio, vm.project.id, request, response)
|
||||
|
||||
@Route.get(
|
||||
r"/qemu/binaries",
|
||||
status_codes={
|
||||
|
@ -261,15 +261,16 @@ class TraceNGHandler:
|
||||
},
|
||||
input=NIO_SCHEMA,
|
||||
output=NIO_SCHEMA,
|
||||
description="Update a NIO from a TraceNG instance")
|
||||
description="Update a NIO on a TraceNG instance")
|
||||
async def update_nio(request, response):
|
||||
|
||||
traceng_manager = TraceNG.instance()
|
||||
vm = traceng_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
nio = vm.ethernet_adapter.get_nio(int(request.match_info["port_number"]))
|
||||
if "filters" in request.json and nio:
|
||||
port_number = int(request.match_info["port_number"])
|
||||
nio = vm.get_nio(port_number)
|
||||
if "filters" in request.json:
|
||||
nio.filters = request.json["filters"]
|
||||
await vm.port_update_nio_binding(int(request.match_info["port_number"]), nio)
|
||||
await vm.port_update_nio_binding(port_number, nio)
|
||||
response.set_status(201)
|
||||
response.json(request.json)
|
||||
|
||||
@ -291,7 +292,8 @@ class TraceNGHandler:
|
||||
|
||||
traceng_manager = TraceNG.instance()
|
||||
vm = traceng_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
await vm.port_remove_nio_binding(int(request.match_info["port_number"]))
|
||||
port_number = int(request.match_info["port_number"])
|
||||
await vm.port_remove_nio_binding(port_number)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
@ -339,3 +341,25 @@ class TraceNGHandler:
|
||||
port_number = int(request.match_info["port_number"])
|
||||
await vm.stop_capture(port_number)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/traceng/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/pcap",
|
||||
description="Stream the pcap capture file",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Adapter to steam a packet capture",
|
||||
"port_number": "Port on the adapter"
|
||||
},
|
||||
status_codes={
|
||||
200: "File returned",
|
||||
403: "Permission denied",
|
||||
404: "The file doesn't exist"
|
||||
})
|
||||
async def stream_pcap_file(request, response):
|
||||
|
||||
traceng_manager = TraceNG.instance()
|
||||
vm = traceng_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
port_number = int(request.match_info["port_number"])
|
||||
nio = vm.get_nio(port_number)
|
||||
await traceng_manager.stream_pcap_file(nio, vm.project.id, request, response)
|
||||
|
@ -309,17 +309,18 @@ class VirtualBoxHandler:
|
||||
},
|
||||
input=NIO_SCHEMA,
|
||||
output=NIO_SCHEMA,
|
||||
description="Update a NIO from a Virtualbox instance")
|
||||
description="Update a NIO on a Virtualbox instance")
|
||||
async def update_nio(request, response):
|
||||
|
||||
virtualbox_manager = VirtualBox.instance()
|
||||
vm = virtualbox_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
nio = vm.ethernet_adapters[int(request.match_info["adapter_number"])]
|
||||
if "filters" in request.json and nio:
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
nio = vm.get_nio(adapter_number)
|
||||
if "filters" in request.json:
|
||||
nio.filters = request.json["filters"]
|
||||
if "suspend" in request.json and nio:
|
||||
if "suspend" in request.json:
|
||||
nio.suspend = request.json["suspend"]
|
||||
await vm.adapter_update_nio_binding(int(request.match_info["adapter_number"]), nio)
|
||||
await vm.adapter_update_nio_binding(adapter_number, nio)
|
||||
response.set_status(201)
|
||||
response.json(request.json)
|
||||
|
||||
@ -341,7 +342,8 @@ class VirtualBoxHandler:
|
||||
|
||||
vbox_manager = VirtualBox.instance()
|
||||
vm = vbox_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
await vm.adapter_remove_nio_binding(int(request.match_info["adapter_number"]))
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
await vm.adapter_remove_nio_binding(adapter_number)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
@ -386,9 +388,32 @@ class VirtualBoxHandler:
|
||||
|
||||
vbox_manager = VirtualBox.instance()
|
||||
vm = vbox_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
await vm.stop_capture(int(request.match_info["adapter_number"]))
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
await vm.stop_capture(adapter_number)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/virtualbox/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/pcap",
|
||||
description="Stream the pcap capture file",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Adapter to steam a packet capture",
|
||||
"port_number": "Port on the adapter (always 0)"
|
||||
},
|
||||
status_codes={
|
||||
200: "File returned",
|
||||
403: "Permission denied",
|
||||
404: "The file doesn't exist"
|
||||
})
|
||||
async def stream_pcap_file(request, response):
|
||||
|
||||
virtualbox_manager = VirtualBox.instance()
|
||||
vm = virtualbox_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
nio = vm.get_nio(adapter_number)
|
||||
await virtualbox_manager.stream_pcap_file(nio, vm.project.id, request, response)
|
||||
|
||||
@Route.get(
|
||||
r"/virtualbox/vms",
|
||||
status_codes={
|
||||
|
@ -276,17 +276,18 @@ class VMwareHandler:
|
||||
},
|
||||
input=NIO_SCHEMA,
|
||||
output=NIO_SCHEMA,
|
||||
description="Update a NIO from a Virtualbox instance")
|
||||
description="Update a NIO on a VMware VM instance")
|
||||
async def update_nio(request, response):
|
||||
|
||||
vmware_manager = VMware.instance()
|
||||
vm = vmware_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
nio = vm.ethernet_adapters[int(request.match_info["adapter_number"])]
|
||||
if "filters" in request.json and nio:
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
nio = vm.get_nio(adapter_number)
|
||||
if "filters" in request.json:
|
||||
nio.filters = request.json["filters"]
|
||||
await vm.adapter_update_nio_binding(int(request.match_info["adapter_number"]), nio)
|
||||
response.set_status(201)
|
||||
response.json(request.json)
|
||||
await vm.adapter_update_nio_binding(adapter_number, nio)
|
||||
response.set_status(201)
|
||||
response.json(request.json)
|
||||
|
||||
@Route.delete(
|
||||
r"/projects/{project_id}/vmware/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/nio",
|
||||
@ -306,7 +307,8 @@ class VMwareHandler:
|
||||
|
||||
vmware_manager = VMware.instance()
|
||||
vm = vmware_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
await vm.adapter_remove_nio_binding(int(request.match_info["adapter_number"]))
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
await vm.adapter_remove_nio_binding(adapter_number)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
@ -355,6 +357,28 @@ class VMwareHandler:
|
||||
await vm.stop_capture(adapter_number)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/vmware/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/pcap",
|
||||
description="Stream the pcap capture file",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Adapter to steam a packet capture",
|
||||
"port_number": "Port on the adapter (always 0)"
|
||||
},
|
||||
status_codes={
|
||||
200: "File returned",
|
||||
403: "Permission denied",
|
||||
404: "The file doesn't exist"
|
||||
})
|
||||
async def stream_pcap_file(request, response):
|
||||
|
||||
vmware_manager = VMware.instance()
|
||||
vm = vmware_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
nio = vm.get_nio(adapter_number)
|
||||
await vmware_manager.stream_pcap_file(nio, vm.project.id, request, response)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/vmware/nodes/{node_id}/interfaces/vmnet",
|
||||
parameters={
|
||||
|
@ -239,8 +239,9 @@ class VPCSHandler:
|
||||
nio_type = request.json["type"]
|
||||
if nio_type not in ("nio_udp", "nio_tap"):
|
||||
raise HTTPConflict(text="NIO of type {} is not supported".format(nio_type))
|
||||
port_number = int(request.match_info["port_number"])
|
||||
nio = vpcs_manager.create_nio(request.json)
|
||||
await vm.port_add_nio_binding(int(request.match_info["port_number"]), nio)
|
||||
await vm.port_add_nio_binding(port_number, nio)
|
||||
response.set_status(201)
|
||||
response.json(nio)
|
||||
|
||||
@ -259,15 +260,16 @@ class VPCSHandler:
|
||||
},
|
||||
input=NIO_SCHEMA,
|
||||
output=NIO_SCHEMA,
|
||||
description="Update a NIO from a VPCS instance")
|
||||
description="Update a NIO on a VPCS instance")
|
||||
async def update_nio(request, response):
|
||||
|
||||
vpcs_manager = VPCS.instance()
|
||||
vm = vpcs_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
nio = vm.ethernet_adapter.get_nio(int(request.match_info["port_number"]))
|
||||
if "filters" in request.json and nio:
|
||||
port_number = int(request.match_info["port_number"])
|
||||
nio = vm.get_nio(port_number)
|
||||
if "filters" in request.json:
|
||||
nio.filters = request.json["filters"]
|
||||
await vm.port_update_nio_binding(int(request.match_info["port_number"]), nio)
|
||||
await vm.port_update_nio_binding(port_number, nio)
|
||||
response.set_status(201)
|
||||
response.json(request.json)
|
||||
|
||||
@ -289,7 +291,8 @@ class VPCSHandler:
|
||||
|
||||
vpcs_manager = VPCS.instance()
|
||||
vm = vpcs_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
await vm.port_remove_nio_binding(int(request.match_info["port_number"]))
|
||||
port_number = int(request.match_info["port_number"])
|
||||
await vm.port_remove_nio_binding(port_number)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
@ -337,3 +340,25 @@ class VPCSHandler:
|
||||
port_number = int(request.match_info["port_number"])
|
||||
await vm.stop_capture(port_number)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/vpcs/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/pcap",
|
||||
description="Stream the pcap capture file",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Adapter to steam a packet capture",
|
||||
"port_number": "Port on the adapter"
|
||||
},
|
||||
status_codes={
|
||||
200: "File returned",
|
||||
403: "Permission denied",
|
||||
404: "The file doesn't exist"
|
||||
})
|
||||
async def stream_pcap_file(request, response):
|
||||
|
||||
vpcs_manager = VPCS.instance()
|
||||
vm = vpcs_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
port_number = int(request.match_info["port_number"])
|
||||
nio = vm.get_nio(port_number)
|
||||
await vpcs_manager.stream_pcap_file(nio, vm.project.id, request, response)
|
||||
|
@ -15,9 +15,8 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import asyncio
|
||||
import aiohttp
|
||||
import multidict
|
||||
|
||||
from gns3server.web.route import Route
|
||||
from gns3server.controller import Controller
|
||||
@ -160,7 +159,8 @@ class LinkHandler:
|
||||
|
||||
project = await Controller.instance().get_loaded_project(request.match_info["project_id"])
|
||||
link = project.get_link(request.match_info["link_id"])
|
||||
await link.start_capture(data_link_type=request.json.get("data_link_type", "DLT_EN10MB"), capture_file_name=request.json.get("capture_file_name"))
|
||||
await link.start_capture(data_link_type=request.json.get("data_link_type", "DLT_EN10MB"),
|
||||
capture_file_name=request.json.get("capture_file_name"))
|
||||
response.set_status(201)
|
||||
response.json(link)
|
||||
|
||||
@ -206,7 +206,7 @@ class LinkHandler:
|
||||
"project_id": "Project UUID",
|
||||
"link_id": "Link UUID"
|
||||
},
|
||||
description="Stream the pcap capture file",
|
||||
description="Stream the PCAP capture file from compute",
|
||||
status_codes={
|
||||
200: "File returned",
|
||||
403: "Permission denied",
|
||||
@ -216,25 +216,25 @@ class LinkHandler:
|
||||
|
||||
project = await Controller.instance().get_loaded_project(request.match_info["project_id"])
|
||||
link = project.get_link(request.match_info["link_id"])
|
||||
if not link.capturing:
|
||||
raise aiohttp.web.HTTPConflict(text="This link has no active packet capture")
|
||||
|
||||
while link.capture_file_path is None:
|
||||
raise aiohttp.web.HTTPNotFound(text="pcap file not found")
|
||||
compute = link.compute
|
||||
pcap_streaming_url = link.pcap_streaming_url()
|
||||
headers = multidict.MultiDict(request.headers)
|
||||
headers['Host'] = compute.host
|
||||
headers['Router-Host'] = request.host
|
||||
body = await request.read()
|
||||
|
||||
while not os.path.isfile(link.capture_file_path):
|
||||
await asyncio.sleep(0.5)
|
||||
connector = aiohttp.TCPConnector(limit=None, force_close=True)
|
||||
async with aiohttp.ClientSession(connector=connector, headers=headers) as session:
|
||||
async with session.request(request.method, pcap_streaming_url, timeout=None, data=body) as response:
|
||||
proxied_response = aiohttp.web.Response(headers=response.headers, status=response.status)
|
||||
if response.headers.get('Transfer-Encoding', '').lower() == 'chunked':
|
||||
proxied_response.enable_chunked_encoding()
|
||||
|
||||
try:
|
||||
with open(link.capture_file_path, "rb") as f:
|
||||
|
||||
response.content_type = "application/vnd.tcpdump.pcap"
|
||||
response.set_status(200)
|
||||
response.enable_chunked_encoding()
|
||||
await response.prepare(request)
|
||||
|
||||
while True:
|
||||
chunk = f.read(4096)
|
||||
if not chunk:
|
||||
await asyncio.sleep(0.1)
|
||||
await response.write(chunk)
|
||||
except OSError:
|
||||
raise aiohttp.web.HTTPNotFound(text="pcap file {} not found or not accessible".format(link.capture_file_path))
|
||||
await proxied_response.prepare(request)
|
||||
async for data in response.content.iter_any():
|
||||
if not data:
|
||||
break
|
||||
await proxied_response.write(data)
|
||||
|
@ -248,9 +248,10 @@ class Route(object):
|
||||
"""
|
||||
To avoid strange effect we prevent concurrency
|
||||
between the same instance of the node
|
||||
(excepting when streaming a PCAP file).
|
||||
"""
|
||||
|
||||
if "node_id" in request.match_info:
|
||||
if "node_id" in request.match_info and not "pcap" in request.path:
|
||||
node_id = request.match_info.get("node_id")
|
||||
|
||||
if "compute" in request.path:
|
||||
|
@ -282,23 +282,6 @@ def test_json_serial_link(async_run, project, compute, link):
|
||||
async_run(link.add_node(node2, 1, 3))
|
||||
assert link.__json__()["link_type"] == "serial"
|
||||
|
||||
|
||||
def test_start_streaming_pcap(link, async_run, tmpdir, project):
|
||||
|
||||
async def fake_reader():
|
||||
output = AsyncioBytesIO()
|
||||
await output.write(b"hello")
|
||||
output.seek(0)
|
||||
return output
|
||||
|
||||
link._capture_file_name = "test.pcap"
|
||||
link._capturing = True
|
||||
link.read_pcap_from_source = fake_reader
|
||||
async_run(link._start_streaming_pcap())
|
||||
with open(os.path.join(project.captures_directory, "test.pcap"), "rb") as f:
|
||||
c = f.read()
|
||||
assert c == b"hello"
|
||||
|
||||
def test_default_capture_file_name(project, compute, async_run):
|
||||
node1 = Node(project, compute, "Hello@", node_type="qemu")
|
||||
node1._ports = [EthernetPort("E0", 0, 0, 4)]
|
||||
|
@ -266,27 +266,6 @@ def test_capture(async_run, project):
|
||||
compute1.post.assert_any_call("/projects/{}/vpcs/nodes/{}/adapters/0/ports/4/stop_capture".format(project.id, node_vpcs.id))
|
||||
|
||||
|
||||
def test_read_pcap_from_source(project, async_run):
|
||||
compute1 = MagicMock()
|
||||
|
||||
node_vpcs = Node(project, compute1, "V1", node_type="vpcs")
|
||||
node_vpcs._status = "started"
|
||||
node_vpcs._ports = [EthernetPort("E0", 0, 0, 4)]
|
||||
node_iou = Node(project, compute1, "I1", node_type="iou")
|
||||
node_iou._ports = [EthernetPort("E0", 0, 3, 1)]
|
||||
|
||||
link = UDPLink(project)
|
||||
link.create = AsyncioMagicMock()
|
||||
async_run(link.add_node(node_vpcs, 0, 4))
|
||||
async_run(link.add_node(node_iou, 3, 1))
|
||||
|
||||
capture = async_run(link.start_capture())
|
||||
assert link._capture_node is not None
|
||||
|
||||
async_run(link.read_pcap_from_source())
|
||||
link._capture_node["node"].compute.stream_file.assert_called_with(project, "tmp/captures/" + link._capture_file_name)
|
||||
|
||||
|
||||
def test_node_updated(project, async_run):
|
||||
"""
|
||||
If a node stop when capturing we stop the capture
|
||||
|
@ -108,3 +108,29 @@ def test_cloud_update(http_compute, vm, tmpdir):
|
||||
example=True)
|
||||
assert response.status == 200
|
||||
assert response.json["name"] == "test"
|
||||
|
||||
|
||||
def test_cloud_start_capture(http_compute, vm):
|
||||
|
||||
with asyncio_patch("gns3server.compute.builtin.nodes.cloud.Cloud.start_capture") as start_capture:
|
||||
params = {"capture_file_name": "test.pcap", "data_link_type": "DLT_EN10MB"}
|
||||
response = http_compute.post("/projects/{project_id}/cloud/nodes/{node_id}/adapters/0/ports/0/start_capture".format(project_id=vm["project_id"], node_id=vm["node_id"]), body=params, example=True)
|
||||
assert response.status == 200
|
||||
assert start_capture.called
|
||||
assert "test.pcap" in response.json["pcap_file_path"]
|
||||
|
||||
|
||||
def test_cloud_stop_capture(http_compute, vm):
|
||||
|
||||
with asyncio_patch("gns3server.compute.builtin.nodes.cloud.Cloud.stop_capture") as stop_capture:
|
||||
response = http_compute.post("/projects/{project_id}/cloud/nodes/{node_id}/adapters/0/ports/0/stop_capture".format(project_id=vm["project_id"], node_id=vm["node_id"]), example=True)
|
||||
assert response.status == 204
|
||||
assert stop_capture.called
|
||||
|
||||
|
||||
def test_cloud_pcap(http_compute, vm, project):
|
||||
|
||||
with asyncio_patch("gns3server.compute.builtin.nodes.cloud.Cloud.get_nio"):
|
||||
with asyncio_patch("gns3server.compute.builtin.Builtin.stream_pcap_file"):
|
||||
response = http_compute.get("/projects/{project_id}/cloud/nodes/{node_id}/adapters/0/ports/0/pcap".format(project_id=project.id, node_id=vm["node_id"]), raw=True)
|
||||
assert response.status == 200
|
||||
|
@ -125,6 +125,12 @@ def test_docker_nio_create_udp(http_compute, vm):
|
||||
|
||||
|
||||
def test_docker_update_nio(http_compute, vm):
|
||||
|
||||
response = http_compute.post("/projects/{project_id}/docker/nodes/{node_id}/adapters/0/ports/0/nio".format(project_id=vm["project_id"], node_id=vm["node_id"]), {"type": "nio_udp",
|
||||
"lport": 4242,
|
||||
"rport": 4343,
|
||||
"rhost": "127.0.0.1"})
|
||||
assert response.status == 201
|
||||
with asyncio_patch("gns3server.compute.docker.docker_vm.DockerVM.adapter_update_nio_binding") as mock:
|
||||
response = http_compute.put("/projects/{project_id}/docker/nodes/{node_id}/adapters/0/ports/0/nio".format(project_id=vm["project_id"], node_id=vm["node_id"]),
|
||||
{
|
||||
@ -166,12 +172,9 @@ def test_docker_start_capture(http_compute, vm, tmpdir, project):
|
||||
|
||||
with patch("gns3server.compute.docker.docker_vm.DockerVM.is_running", return_value=True) as mock:
|
||||
with asyncio_patch("gns3server.compute.docker.docker_vm.DockerVM.start_capture") as start_capture:
|
||||
|
||||
params = {"capture_file_name": "test.pcap", "data_link_type": "DLT_EN10MB"}
|
||||
response = http_compute.post("/projects/{project_id}/docker/nodes/{node_id}/adapters/0/ports/0/start_capture".format(project_id=vm["project_id"], node_id=vm["node_id"]), body=params, example=True)
|
||||
|
||||
assert response.status == 200
|
||||
|
||||
assert start_capture.called
|
||||
assert "test.pcap" in response.json["pcap_file_path"]
|
||||
|
||||
@ -180,11 +183,8 @@ def test_docker_stop_capture(http_compute, vm, tmpdir, project):
|
||||
|
||||
with patch("gns3server.compute.docker.docker_vm.DockerVM.is_running", return_value=True) as mock:
|
||||
with asyncio_patch("gns3server.compute.docker.docker_vm.DockerVM.stop_capture") as stop_capture:
|
||||
|
||||
response = http_compute.post("/projects/{project_id}/docker/nodes/{node_id}/adapters/0/ports/0/stop_capture".format(project_id=vm["project_id"], node_id=vm["node_id"]), example=True)
|
||||
|
||||
assert response.status == 204
|
||||
|
||||
assert stop_capture.called
|
||||
|
||||
|
||||
|
@ -290,6 +290,14 @@ def test_iou_stop_capture(http_compute, vm, tmpdir, project):
|
||||
assert stop_capture.called
|
||||
|
||||
|
||||
def test_iou_pcap(http_compute, vm, project):
|
||||
|
||||
with asyncio_patch("gns3server.compute.iou.iou_vm.IOUVM.get_nio"):
|
||||
with asyncio_patch("gns3server.compute.iou.IOU.stream_pcap_file"):
|
||||
response = http_compute.get("/projects/{project_id}/iou/nodes/{node_id}/adapters/0/ports/0/pcap".format(project_id=project.id, node_id=vm["node_id"]), raw=True)
|
||||
assert response.status == 200
|
||||
|
||||
|
||||
def test_images(http_compute, fake_iou_bin):
|
||||
|
||||
response = http_compute.get("/iou/images", example=True)
|
||||
|
@ -112,3 +112,29 @@ def test_nat_update(http_compute, vm, tmpdir):
|
||||
example=True)
|
||||
assert response.status == 200
|
||||
assert response.json["name"] == "test"
|
||||
|
||||
|
||||
def test_nat_start_capture(http_compute, vm):
|
||||
|
||||
with asyncio_patch("gns3server.compute.builtin.nodes.nat.Nat.start_capture") as start_capture:
|
||||
params = {"capture_file_name": "test.pcap", "data_link_type": "DLT_EN10MB"}
|
||||
response = http_compute.post("/projects/{project_id}/nat/nodes/{node_id}/adapters/0/ports/0/start_capture".format(project_id=vm["project_id"], node_id=vm["node_id"]), body=params, example=True)
|
||||
assert response.status == 200
|
||||
assert start_capture.called
|
||||
assert "test.pcap" in response.json["pcap_file_path"]
|
||||
|
||||
|
||||
def test_nat_stop_capture(http_compute, vm):
|
||||
|
||||
with asyncio_patch("gns3server.compute.builtin.nodes.nat.Nat.stop_capture") as stop_capture:
|
||||
response = http_compute.post("/projects/{project_id}/nat/nodes/{node_id}/adapters/0/ports/0/stop_capture".format(project_id=vm["project_id"], node_id=vm["node_id"]), example=True)
|
||||
assert response.status == 204
|
||||
assert stop_capture.called
|
||||
|
||||
|
||||
def test_nat_pcap(http_compute, vm, project):
|
||||
|
||||
with asyncio_patch("gns3server.compute.builtin.nodes.nat.Nat.get_nio"):
|
||||
with asyncio_patch("gns3server.compute.builtin.Builtin.stream_pcap_file"):
|
||||
response = http_compute.get("/projects/{project_id}/nat/nodes/{node_id}/adapters/0/ports/0/pcap".format(project_id=project.id, node_id=vm["node_id"]), raw=True)
|
||||
assert response.status == 200
|
||||
|
@ -361,3 +361,31 @@ def test_qemu_duplicate(http_compute, vm):
|
||||
example=True)
|
||||
assert mock.called
|
||||
assert response.status == 201
|
||||
|
||||
|
||||
def test_qemu_start_capture(http_compute, vm):
|
||||
|
||||
with patch("gns3server.compute.qemu.qemu_vm.QemuVM.is_running", return_value=True):
|
||||
with asyncio_patch("gns3server.compute.qemu.qemu_vm.QemuVM.start_capture") as start_capture:
|
||||
params = {"capture_file_name": "test.pcap", "data_link_type": "DLT_EN10MB"}
|
||||
response = http_compute.post("/projects/{project_id}/qemu/nodes/{node_id}/adapters/0/ports/0/start_capture".format(project_id=vm["project_id"], node_id=vm["node_id"]), body=params, example=True)
|
||||
assert response.status == 200
|
||||
assert start_capture.called
|
||||
assert "test.pcap" in response.json["pcap_file_path"]
|
||||
|
||||
|
||||
def test_qemu_stop_capture(http_compute, vm):
|
||||
|
||||
with patch("gns3server.compute.qemu.qemu_vm.QemuVM.is_running", return_value=True):
|
||||
with asyncio_patch("gns3server.compute.qemu.qemu_vm.QemuVM.stop_capture") as stop_capture:
|
||||
response = http_compute.post("/projects/{project_id}/qemu/nodes/{node_id}/adapters/0/ports/0/stop_capture".format(project_id=vm["project_id"], node_id=vm["node_id"]), example=True)
|
||||
assert response.status == 204
|
||||
assert stop_capture.called
|
||||
|
||||
|
||||
def test_qemu_pcap(http_compute, vm, project):
|
||||
|
||||
with asyncio_patch("gns3server.compute.qemu.qemu_vm.QemuVM.get_nio"):
|
||||
with asyncio_patch("gns3server.compute.qemu.Qemu.stream_pcap_file"):
|
||||
response = http_compute.get("/projects/{project_id}/qemu/nodes/{node_id}/adapters/0/ports/0/pcap".format(project_id=project.id, node_id=vm["node_id"]), raw=True)
|
||||
assert response.status == 200
|
||||
|
@ -60,6 +60,13 @@ def test_traceng_nio_create_udp(http_compute, vm):
|
||||
|
||||
|
||||
def test_traceng_nio_update_udp(http_compute, vm):
|
||||
|
||||
with asyncio_patch("gns3server.compute.traceng.traceng_vm.TraceNGVM.add_ubridge_udp_connection"):
|
||||
response = http_compute.post("/projects/{project_id}/traceng/nodes/{node_id}/adapters/0/ports/0/nio".format(project_id=vm["project_id"], node_id=vm["node_id"]), {"type": "nio_udp",
|
||||
"lport": 4242,
|
||||
"rport": 4343,
|
||||
"rhost": "127.0.0.1"})
|
||||
assert response.status == 201
|
||||
response = http_compute.put("/projects/{project_id}/traceng/nodes/{node_id}/adapters/0/ports/0/nio".format(project_id=vm["project_id"], node_id=vm["node_id"]),
|
||||
{
|
||||
"type": "nio_udp",
|
||||
@ -136,3 +143,31 @@ def test_traceng_update(http_compute, vm, tmpdir, free_console_port):
|
||||
assert response.status == 200
|
||||
assert response.json["name"] == "test"
|
||||
assert response.json["ip_address"] == "192.168.1.1"
|
||||
|
||||
|
||||
def test_traceng_start_capture(http_compute, vm):
|
||||
|
||||
with patch("gns3server.compute.traceng.traceng_vm.TraceNGVM.is_running", return_value=True):
|
||||
with asyncio_patch("gns3server.compute.traceng.traceng_vm.TraceNGVM.start_capture") as start_capture:
|
||||
params = {"capture_file_name": "test.pcap", "data_link_type": "DLT_EN10MB"}
|
||||
response = http_compute.post("/projects/{project_id}/traceng/nodes/{node_id}/adapters/0/ports/0/start_capture".format(project_id=vm["project_id"], node_id=vm["node_id"]), body=params, example=True)
|
||||
assert response.status == 200
|
||||
assert start_capture.called
|
||||
assert "test.pcap" in response.json["pcap_file_path"]
|
||||
|
||||
|
||||
def test_traceng_stop_capture(http_compute, vm):
|
||||
|
||||
with patch("gns3server.compute.traceng.traceng_vm.TraceNGVM.is_running", return_value=True):
|
||||
with asyncio_patch("gns3server.compute.traceng.traceng_vm.TraceNGVM.stop_capture") as stop_capture:
|
||||
response = http_compute.post("/projects/{project_id}/traceng/nodes/{node_id}/adapters/0/ports/0/stop_capture".format(project_id=vm["project_id"], node_id=vm["node_id"]), example=True)
|
||||
assert response.status == 204
|
||||
assert stop_capture.called
|
||||
|
||||
|
||||
def test_traceng_pcap(http_compute, vm, project):
|
||||
|
||||
with asyncio_patch("gns3server.compute.traceng.traceng_vm.TraceNGVM.get_nio"):
|
||||
with asyncio_patch("gns3server.compute.traceng.TraceNG.stream_pcap_file"):
|
||||
response = http_compute.get("/projects/{project_id}/traceng/nodes/{node_id}/adapters/0/ports/0/pcap".format(project_id=project.id, node_id=vm["node_id"]), raw=True)
|
||||
assert response.status == 200
|
||||
|
@ -113,6 +113,7 @@ def test_vbox_nio_create_udp(http_compute, vm):
|
||||
|
||||
|
||||
def test_virtualbox_nio_update_udp(http_compute, vm):
|
||||
|
||||
with asyncio_patch('gns3server.compute.virtualbox.virtualbox_vm.VirtualBoxVM.ethernet_adapters'):
|
||||
with asyncio_patch('gns3server.compute.virtualbox.virtualbox_vm.VirtualBoxVM.adapter_remove_nio_binding') as mock:
|
||||
response = http_compute.put("/projects/{project_id}/virtualbox/nodes/{node_id}/adapters/0/ports/0/nio".format(
|
||||
@ -150,3 +151,31 @@ def test_vbox_update(http_compute, vm, free_console_port):
|
||||
assert response.status == 200
|
||||
assert response.json["name"] == "test"
|
||||
assert response.json["console"] == free_console_port
|
||||
|
||||
|
||||
def test_virtualbox_start_capture(http_compute, vm):
|
||||
|
||||
with patch("gns3server.compute.virtualbox.virtualbox_vm.VirtualBoxVM.is_running", return_value=True):
|
||||
with asyncio_patch("gns3server.compute.virtualbox.virtualbox_vm.VirtualBoxVM.start_capture") as start_capture:
|
||||
params = {"capture_file_name": "test.pcap", "data_link_type": "DLT_EN10MB"}
|
||||
response = http_compute.post("/projects/{project_id}/virtualbox/nodes/{node_id}/adapters/0/ports/0/start_capture".format(project_id=vm["project_id"], node_id=vm["node_id"]), body=params, example=True)
|
||||
assert response.status == 200
|
||||
assert start_capture.called
|
||||
assert "test.pcap" in response.json["pcap_file_path"]
|
||||
|
||||
|
||||
def test_virtualbox_stop_capture(http_compute, vm):
|
||||
|
||||
with patch("gns3server.compute.virtualbox.virtualbox_vm.VirtualBoxVM.is_running", return_value=True):
|
||||
with asyncio_patch("gns3server.compute.virtualbox.virtualbox_vm.VirtualBoxVM.stop_capture") as stop_capture:
|
||||
response = http_compute.post("/projects/{project_id}/virtualbox/nodes/{node_id}/adapters/0/ports/0/stop_capture".format(project_id=vm["project_id"], node_id=vm["node_id"]), example=True)
|
||||
assert response.status == 204
|
||||
assert stop_capture.called
|
||||
|
||||
|
||||
def test_virtualbox_pcap(http_compute, vm, project):
|
||||
|
||||
with asyncio_patch("gns3server.compute.virtualbox.virtualbox_vm.VirtualBoxVM.get_nio"):
|
||||
with asyncio_patch("gns3server.compute.virtualbox.VirtualBox.stream_pcap_file"):
|
||||
response = http_compute.get("/projects/{project_id}/virtualbox/nodes/{node_id}/adapters/0/ports/0/pcap".format(project_id=project.id, node_id=vm["node_id"]), raw=True)
|
||||
assert response.status == 200
|
||||
|
@ -159,3 +159,30 @@ def test_vmware_update(http_compute, vm, free_console_port):
|
||||
assert response.status == 200
|
||||
assert response.json["name"] == "test"
|
||||
assert response.json["console"] == free_console_port
|
||||
|
||||
def test_vmware_start_capture(http_compute, vm):
|
||||
|
||||
with patch("gns3server.compute.vmware.vmware_vm.VMwareVM.is_running", return_value=True):
|
||||
with asyncio_patch("gns3server.compute.vmware.vmware_vm.VMwareVM.start_capture") as start_capture:
|
||||
params = {"capture_file_name": "test.pcap", "data_link_type": "DLT_EN10MB"}
|
||||
response = http_compute.post("/projects/{project_id}/vmware/nodes/{node_id}/adapters/0/ports/0/start_capture".format(project_id=vm["project_id"], node_id=vm["node_id"]), body=params, example=True)
|
||||
assert response.status == 200
|
||||
assert start_capture.called
|
||||
assert "test.pcap" in response.json["pcap_file_path"]
|
||||
|
||||
|
||||
def test_vmware_stop_capture(http_compute, vm):
|
||||
|
||||
with patch("gns3server.compute.vmware.vmware_vm.VMwareVM.is_running", return_value=True):
|
||||
with asyncio_patch("gns3server.compute.vmware.vmware_vm.VMwareVM.stop_capture") as stop_capture:
|
||||
response = http_compute.post("/projects/{project_id}/vmware/nodes/{node_id}/adapters/0/ports/0/stop_capture".format(project_id=vm["project_id"], node_id=vm["node_id"]), example=True)
|
||||
assert response.status == 204
|
||||
assert stop_capture.called
|
||||
|
||||
|
||||
def test_vmware_pcap(http_compute, vm, project):
|
||||
|
||||
with asyncio_patch("gns3server.compute.vmware.vmware_vm.VMwareVM.get_nio"):
|
||||
with asyncio_patch("gns3server.compute.vmware.VMware.stream_pcap_file"):
|
||||
response = http_compute.get("/projects/{project_id}/vmware/nodes/{node_id}/adapters/0/ports/0/pcap".format(project_id=project.id, node_id=vm["node_id"]), raw=True)
|
||||
assert response.status == 200
|
||||
|
@ -17,8 +17,6 @@
|
||||
|
||||
import pytest
|
||||
import uuid
|
||||
import sys
|
||||
import os
|
||||
from tests.utils import asyncio_patch
|
||||
from unittest.mock import patch
|
||||
|
||||
@ -77,6 +75,13 @@ def test_vpcs_nio_create_udp(http_compute, vm):
|
||||
|
||||
|
||||
def test_vpcs_nio_update_udp(http_compute, vm):
|
||||
|
||||
with asyncio_patch("gns3server.compute.vpcs.vpcs_vm.VPCSVM.add_ubridge_udp_connection"):
|
||||
response = http_compute.post("/projects/{project_id}/vpcs/nodes/{node_id}/adapters/0/ports/0/nio".format(project_id=vm["project_id"], node_id=vm["node_id"]), {"type": "nio_udp",
|
||||
"lport": 4242,
|
||||
"rport": 4343,
|
||||
"rhost": "127.0.0.1"})
|
||||
assert response.status == 201
|
||||
response = http_compute.put("/projects/{project_id}/vpcs/nodes/{node_id}/adapters/0/ports/0/nio".format(project_id=vm["project_id"], node_id=vm["node_id"]),
|
||||
{
|
||||
"type": "nio_udp",
|
||||
@ -153,3 +158,31 @@ def test_vpcs_update(http_compute, vm, tmpdir, free_console_port):
|
||||
assert response.status == 200
|
||||
assert response.json["name"] == "test"
|
||||
assert response.json["console"] == free_console_port
|
||||
|
||||
|
||||
def test_vpcs_start_capture(http_compute, vm):
|
||||
|
||||
with patch("gns3server.compute.vpcs.vpcs_vm.VPCSVM.is_running", return_value=True):
|
||||
with asyncio_patch("gns3server.compute.vpcs.vpcs_vm.VPCSVM.start_capture") as start_capture:
|
||||
params = {"capture_file_name": "test.pcap", "data_link_type": "DLT_EN10MB"}
|
||||
response = http_compute.post("/projects/{project_id}/vpcs/nodes/{node_id}/adapters/0/ports/0/start_capture".format(project_id=vm["project_id"], node_id=vm["node_id"]), body=params, example=True)
|
||||
assert response.status == 200
|
||||
assert start_capture.called
|
||||
assert "test.pcap" in response.json["pcap_file_path"]
|
||||
|
||||
|
||||
def test_vpcs_stop_capture(http_compute, vm):
|
||||
|
||||
with patch("gns3server.compute.vpcs.vpcs_vm.VPCSVM.is_running", return_value=True):
|
||||
with asyncio_patch("gns3server.compute.vpcs.vpcs_vm.VPCSVM.stop_capture") as stop_capture:
|
||||
response = http_compute.post("/projects/{project_id}/vpcs/nodes/{node_id}/adapters/0/ports/0/stop_capture".format(project_id=vm["project_id"], node_id=vm["node_id"]), example=True)
|
||||
assert response.status == 204
|
||||
assert stop_capture.called
|
||||
|
||||
|
||||
def test_vpcs_pcap(http_compute, vm, project):
|
||||
|
||||
with asyncio_patch("gns3server.compute.vpcs.vpcs_vm.VPCSVM.get_nio"):
|
||||
with asyncio_patch("gns3server.compute.vpcs.VPCS.stream_pcap_file"):
|
||||
response = http_compute.get("/projects/{project_id}/vpcs/nodes/{node_id}/adapters/0/ports/0/pcap".format(project_id=project.id, node_id=vm["node_id"]), raw=True)
|
||||
assert response.status == 200
|
||||
|
@ -340,22 +340,23 @@ def test_stop_capture(http_controller, tmpdir, project, compute, async_run):
|
||||
assert response.status == 201
|
||||
|
||||
|
||||
def test_pcap(http_controller, tmpdir, project, compute, async_run):
|
||||
|
||||
async def go():
|
||||
async with aiohttp.request("GET", http_controller.get_url("/projects/{}/links/{}/pcap".format(project.id, link.id))) as response:
|
||||
response.body = await response.content.read(5)
|
||||
return response
|
||||
|
||||
link = Link(project)
|
||||
link._capture_file_name = "test"
|
||||
link._capturing = True
|
||||
with open(link.capture_file_path, "w+") as f:
|
||||
f.write("hello")
|
||||
project._links = {link.id: link}
|
||||
response = async_run(asyncio.ensure_future(go()))
|
||||
assert response.status == 200
|
||||
assert b'hello' == response.body
|
||||
# def test_pcap(http_controller, tmpdir, project, compute, async_run):
|
||||
#
|
||||
# async def go():
|
||||
# async with aiohttp.request("GET", http_controller.get_url("/projects/{}/links/{}/pcap".format(project.id, link.id))) as response:
|
||||
# response.body = await response.content.read(5)
|
||||
# return response
|
||||
#
|
||||
# with asyncio_patch("gns3server.controller.link.Link.capture_node") as mock:
|
||||
# link = Link(project)
|
||||
# link._capture_file_name = "test"
|
||||
# link._capturing = True
|
||||
# with open(link.capture_file_path, "w+") as f:
|
||||
# f.write("hello")
|
||||
# project._links = {link.id: link}
|
||||
# response = async_run(asyncio.ensure_future(go()))
|
||||
# assert response.status == 200
|
||||
# assert b'hello' == response.body
|
||||
|
||||
|
||||
def test_delete_link(http_controller, tmpdir, project, compute, async_run):
|
||||
|
Loading…
Reference in New Issue
Block a user