mirror of
https://github.com/GNS3/gns3-server
synced 2024-11-28 11:18:11 +00:00
Merge remote-tracking branch 'origin/asyncio' into asyncio
This commit is contained in:
commit
516b882122
@ -15,12 +15,16 @@
|
||||
# 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
|
||||
|
||||
|
||||
from ..web.route import Route
|
||||
from ..modules.port_manager import PortManager
|
||||
from ..schemas.iou import IOU_CREATE_SCHEMA
|
||||
from ..schemas.iou import IOU_UPDATE_SCHEMA
|
||||
from ..schemas.iou import IOU_OBJECT_SCHEMA
|
||||
from ..schemas.iou import IOU_NIO_SCHEMA
|
||||
from ..schemas.iou import IOU_CAPTURE_SCHEMA
|
||||
from ..modules.iou import IOU
|
||||
|
||||
|
||||
@ -216,7 +220,7 @@ class IOUHandler:
|
||||
iou_manager = IOU.instance()
|
||||
vm = iou_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
|
||||
nio = iou_manager.create_nio(vm.iouyap_path, request.json)
|
||||
vm.slot_add_nio_binding(int(request.match_info["adapter_number"]), int(request.match_info["port_number"]), nio)
|
||||
vm.adapter_add_nio_binding(int(request.match_info["adapter_number"]), int(request.match_info["port_number"]), nio)
|
||||
response.set_status(201)
|
||||
response.json(nio)
|
||||
|
||||
@ -239,5 +243,53 @@ class IOUHandler:
|
||||
|
||||
iou_manager = IOU.instance()
|
||||
vm = iou_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
|
||||
vm.slot_remove_nio_binding(int(request.match_info["adapter_number"]), int(request.match_info["port_number"]))
|
||||
vm.adapter_remove_nio_binding(int(request.match_info["adapter_number"]), int(request.match_info["port_number"]))
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/iou/vms/{vm_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/start_capture",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance",
|
||||
"adapter_number": "Adapter to start a packet capture",
|
||||
"port_number": "Port on the adapter"
|
||||
},
|
||||
status_codes={
|
||||
200: "Capture started",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Start a packet capture on a IOU VM instance",
|
||||
input=IOU_CAPTURE_SCHEMA)
|
||||
def start_capture(request, response):
|
||||
|
||||
iou_manager = IOU.instance()
|
||||
vm = iou_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
port_number = int(request.match_info["port_number"])
|
||||
pcap_file_path = os.path.join(vm.project.capture_working_directory(), request.json["capture_file_name"])
|
||||
yield from vm.start_capture(adapter_number, port_number, pcap_file_path, request.json["data_link_type"])
|
||||
response.json({"pcap_file_path": pcap_file_path})
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/iou/vms/{vm_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/stop_capture",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance",
|
||||
"adapter_number": "Adapter to stop a packet capture",
|
||||
"port_number": "Port on the adapter (always 0)"
|
||||
},
|
||||
status_codes={
|
||||
204: "Capture stopped",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Stop a packet capture on a IOU VM instance")
|
||||
def stop_capture(request, response):
|
||||
|
||||
iou_manager = IOU.instance()
|
||||
vm = iou_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
port_number = int(request.match_info["port_number"])
|
||||
yield from vm.stop_capture(adapter_number, port_number)
|
||||
response.set_status(204)
|
||||
|
@ -445,7 +445,7 @@ class IOUVM(BaseVM):
|
||||
"base_port": "49000"}
|
||||
|
||||
bay_id = 0
|
||||
for adapter in self._slots:
|
||||
for adapter in self._adapters:
|
||||
unit_id = 0
|
||||
for unit in adapter.ports.keys():
|
||||
nio = adapter.get_nio(unit)
|
||||
@ -716,7 +716,7 @@ class IOUVM(BaseVM):
|
||||
id=self._id,
|
||||
adapters=len(self._ethernet_adapters)))
|
||||
|
||||
self._slots = self._ethernet_adapters + self._serial_adapters
|
||||
self._adapters = self._ethernet_adapters + self._serial_adapters
|
||||
|
||||
@property
|
||||
def serial_adapters(self):
|
||||
@ -742,20 +742,20 @@ class IOUVM(BaseVM):
|
||||
id=self._id,
|
||||
adapters=len(self._serial_adapters)))
|
||||
|
||||
self._slots = self._ethernet_adapters + self._serial_adapters
|
||||
self._adapters = self._ethernet_adapters + self._serial_adapters
|
||||
|
||||
def slot_add_nio_binding(self, adapter_number, port_number, nio):
|
||||
def adapter_add_nio_binding(self, adapter_number, port_number, nio):
|
||||
"""
|
||||
Adds a slot NIO binding.
|
||||
:param adapter_number: slot ID
|
||||
Adds a adapter NIO binding.
|
||||
:param adapter_number: adapter ID
|
||||
:param port_number: port ID
|
||||
:param nio: NIO instance to add to the slot/port
|
||||
:param nio: NIO instance to add to the adapter/port
|
||||
"""
|
||||
|
||||
try:
|
||||
adapter = self._slots[adapter_number]
|
||||
adapter = self._adapters[adapter_number]
|
||||
except IndexError:
|
||||
raise IOUError("Slot {adapter_number} doesn't exist on IOU {name}".format(name=self._name,
|
||||
raise IOUError("Adapter {adapter_number} doesn't exist on IOU {name}".format(name=self._name,
|
||||
adapter_number=adapter_number))
|
||||
|
||||
if not adapter.port_exists(port_number):
|
||||
@ -772,18 +772,18 @@ class IOUVM(BaseVM):
|
||||
self._update_iouyap_config()
|
||||
os.kill(self._iouyap_process.pid, signal.SIGHUP)
|
||||
|
||||
def slot_remove_nio_binding(self, adapter_number, port_number):
|
||||
def adapter_remove_nio_binding(self, adapter_number, port_number):
|
||||
"""
|
||||
Removes a slot NIO binding.
|
||||
:param adapter_number: slot ID
|
||||
Removes a adapter NIO binding.
|
||||
:param adapter_number: adapter ID
|
||||
:param port_number: port ID
|
||||
:returns: NIO instance
|
||||
"""
|
||||
|
||||
try:
|
||||
adapter = self._slots[adapter_number]
|
||||
adapter = self._adapters[adapter_number]
|
||||
except IndexError:
|
||||
raise IOUError("Slot {adapter_number} doesn't exist on IOU {name}".format(name=self._name,
|
||||
raise IOUError("Adapter {adapter_number} doesn't exist on IOU {name}".format(name=self._name,
|
||||
adapter_number=adapter_number))
|
||||
|
||||
if not adapter.port_exists(port_number):
|
||||
@ -889,3 +889,73 @@ class IOUVM(BaseVM):
|
||||
return path
|
||||
else:
|
||||
return None
|
||||
|
||||
def start_capture(self, adapter_number, port_number, output_file, data_link_type="DLT_EN10MB"):
|
||||
"""
|
||||
Starts a packet capture.
|
||||
:param adapter_number: adapter ID
|
||||
:param port_number: port ID
|
||||
:param port: allocated port
|
||||
:param output_file: PCAP destination file for the capture
|
||||
: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} doesn't exist on IOU {name}".format(name=self._name,
|
||||
adapter_number=adapter_number))
|
||||
|
||||
if not adapter.port_exists(port_number):
|
||||
raise IOUError("Port {port_number} doesn't exist in adapter {adapter}".format(adapter=adapter,
|
||||
port_number=port_number))
|
||||
|
||||
nio = adapter.get_nio(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))
|
||||
|
||||
try:
|
||||
os.makedirs(os.path.dirname(output_file))
|
||||
except FileExistsError:
|
||||
pass
|
||||
except OSError as e:
|
||||
raise IOUError("Could not create captures directory {}".format(e))
|
||||
|
||||
nio.startPacketCapture(output_file, data_link_type)
|
||||
|
||||
log.info("IOU {name} [id={id}]: starting packet capture on {adapter_number}/{port_number}".format(name=self._name,
|
||||
id=self._id,
|
||||
adapter_number=adapter_number,
|
||||
port_number=port_number))
|
||||
|
||||
if self.is_iouyap_running():
|
||||
self._update_iouyap_config()
|
||||
os.kill(self._iouyap_process.pid, signal.SIGHUP)
|
||||
|
||||
def stop_capture(self, adapter_number, port_number):
|
||||
"""
|
||||
Stops a packet capture.
|
||||
:param adapter_number: adapter ID
|
||||
:param port_number: port ID
|
||||
"""
|
||||
|
||||
try:
|
||||
adapter = self._adapters[adapter_number]
|
||||
except IndexError:
|
||||
raise IOUError("Adapter {adapter_number} doesn't exist on IOU {name}".format(name=self._name,
|
||||
adapter_number=adapter_number))
|
||||
|
||||
if not adapter.port_exists(port_number):
|
||||
raise IOUError("Port {port_number} doesn't exist in adapter {adapter}".format(adapter=adapter,
|
||||
port_number=port_number))
|
||||
|
||||
nio = adapter.get_nio(port_number)
|
||||
nio.stopPacketCapture()
|
||||
log.info("IOU {name} [id={id}]: stopping packet capture on {adapter_number}/{port_number}".format(name=self._name,
|
||||
id=self._id,
|
||||
adapter_number=adapter_number,
|
||||
port_number=port_number))
|
||||
if self.is_iouyap_running():
|
||||
self._update_iouyap_config()
|
||||
os.kill(self._iouyap_process.pid, signal.SIGHUP)
|
||||
|
@ -23,33 +23,35 @@ Base interface for NIOs.
|
||||
class NIO(object):
|
||||
|
||||
"""
|
||||
Network Input/Output.
|
||||
IOU NIO.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
|
||||
self._capturing = False
|
||||
self._pcap_output_file = ""
|
||||
self._pcap_data_link_type = ""
|
||||
|
||||
def startPacketCapture(self, pcap_output_file):
|
||||
def startPacketCapture(self, pcap_output_file, pcap_data_link_type="DLT_EN10MB"):
|
||||
"""
|
||||
|
||||
:param pcap_output_file: PCAP destination file for the capture
|
||||
:param pcap_data_link_type: PCAP data link type (DLT_*), default is DLT_EN10MB
|
||||
"""
|
||||
|
||||
self._capturing = True
|
||||
self._pcap_output_file = pcap_output_file
|
||||
self._pcap_data_link_type = pcap_data_link_type
|
||||
|
||||
def stopPacketCapture(self):
|
||||
|
||||
self._capturing = False
|
||||
self._pcap_output_file = ""
|
||||
self._pcap_data_link_type = ""
|
||||
|
||||
@property
|
||||
def capturing(self):
|
||||
"""
|
||||
Returns either a capture is configured on this NIO.
|
||||
|
||||
:returns: boolean
|
||||
"""
|
||||
|
||||
@ -59,8 +61,16 @@ class NIO(object):
|
||||
def pcap_output_file(self):
|
||||
"""
|
||||
Returns the path to the PCAP output file.
|
||||
|
||||
:returns: path to the PCAP output file
|
||||
"""
|
||||
|
||||
return self._pcap_output_file
|
||||
|
||||
@property
|
||||
def pcap_data_link_type(self):
|
||||
"""
|
||||
Returns the PCAP data link type
|
||||
:returns: PCAP data link type (DLT_* value)
|
||||
"""
|
||||
|
||||
return self._pcap_data_link_type
|
||||
|
@ -103,10 +103,6 @@ IOU_UPDATE_SCHEMA = {
|
||||
"description": "Path of iourc",
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"initial_config": {
|
||||
"description": "Initial configuration path",
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"serial_adapters": {
|
||||
"description": "How many serial adapters are connected to the IOU",
|
||||
"type": ["integer", "null"]
|
||||
@ -265,3 +261,23 @@ IOU_NIO_SCHEMA = {
|
||||
"additionalProperties": True,
|
||||
"required": ["type"]
|
||||
}
|
||||
|
||||
IOU_CAPTURE_SCHEMA = {
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"description": "Request validation to start a packet capture on a IOU instance",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"capture_file_name": {
|
||||
"description": "Capture file name",
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
},
|
||||
"data_link_type": {
|
||||
"description": "PCAP data link type",
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
},
|
||||
},
|
||||
"additionalProperties": False,
|
||||
"required": ["capture_file_name", "data_link_type"]
|
||||
}
|
||||
|
@ -201,3 +201,25 @@ def test_iou_delete_nio(server, vm):
|
||||
response = server.delete("/projects/{project_id}/iou/vms/{vm_id}/adapters/1/ports/0/nio".format(project_id=vm["project_id"], vm_id=vm["vm_id"]), example=True)
|
||||
assert response.status == 204
|
||||
assert response.route == "/projects/{project_id}/iou/vms/{vm_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/nio"
|
||||
|
||||
|
||||
def test_iou_start_capture(server, vm, tmpdir):
|
||||
|
||||
with asyncio_patch("gns3server.modules.iou.iou_vm.IOUVM.start_capture", return_value=True) as mock:
|
||||
|
||||
params = {"capture_file_name": "test.pcap", "data_link_type": "DLT_EN10MB"}
|
||||
response = server.post("/projects/{project_id}/iou/vms/{vm_id}/adapters/0/ports/0/start_capture".format(project_id=vm["project_id"], vm_id=vm["vm_id"]), body=params)
|
||||
|
||||
assert mock.called
|
||||
assert response.status == 200
|
||||
assert "test.pcap" in response.json["pcap_file_path"]
|
||||
|
||||
|
||||
def test_iou_stop_capture(server, vm, tmpdir):
|
||||
|
||||
with asyncio_patch("gns3server.modules.iou.iou_vm.IOUVM.stop_capture", return_value=True) as mock:
|
||||
|
||||
response = server.post("/projects/{project_id}/iou/vms/{vm_id}/adapters/0/ports/0/stop_capture".format(project_id=vm["project_id"], vm_id=vm["vm_id"]))
|
||||
|
||||
assert mock.called
|
||||
assert response.status == 204
|
||||
|
@ -142,12 +142,12 @@ def test_reload(loop, vm, fake_iou_bin):
|
||||
process.terminate.assert_called_with()
|
||||
|
||||
|
||||
def test_close(vm, port_manager):
|
||||
def test_close(vm, port_manager, loop):
|
||||
with asyncio_patch("gns3server.modules.iou.iou_vm.IOUVM._check_requirements", return_value=True):
|
||||
with asyncio_patch("asyncio.create_subprocess_exec", return_value=MagicMock()):
|
||||
vm.start()
|
||||
port = vm.console
|
||||
vm.close()
|
||||
loop.run_until_complete(asyncio.async(vm.close()))
|
||||
# Raise an exception if the port is not free
|
||||
port_manager.reserve_console_port(port)
|
||||
assert vm.is_running() is False
|
||||
@ -258,3 +258,23 @@ def test_enable_l1_keepalives(loop, vm):
|
||||
with pytest.raises(IOUError):
|
||||
loop.run_until_complete(asyncio.async(vm._enable_l1_keepalives(command)))
|
||||
assert command == ["test"]
|
||||
|
||||
|
||||
def test_start_capture(vm, tmpdir, manager, free_console_port):
|
||||
|
||||
output_file = str(tmpdir / "test.pcap")
|
||||
nio = manager.create_nio(vm.iouyap_path, {"type": "nio_udp", "lport": free_console_port, "rport": free_console_port, "rhost": "192.168.1.2"})
|
||||
vm.adapter_add_nio_binding(0, 0, nio)
|
||||
vm.start_capture(0, 0, output_file)
|
||||
assert vm._adapters[0].get_nio(0).capturing
|
||||
|
||||
|
||||
def test_stop_capture(vm, tmpdir, manager, free_console_port):
|
||||
|
||||
output_file = str(tmpdir / "test.pcap")
|
||||
nio = manager.create_nio(vm.iouyap_path, {"type": "nio_udp", "lport": free_console_port, "rport": free_console_port, "rhost": "192.168.1.2"})
|
||||
vm.adapter_add_nio_binding(0, 0, nio)
|
||||
vm.start_capture(0, 0, output_file)
|
||||
assert vm._adapters[0].get_nio(0).capturing
|
||||
vm.stop_capture(0, 0)
|
||||
assert vm._adapters[0].get_nio(0).capturing == False
|
||||
|
@ -212,12 +212,12 @@ def test_change_name(vm, tmpdir):
|
||||
assert f.read() == "name hello"
|
||||
|
||||
|
||||
def test_close(vm, port_manager):
|
||||
def test_close(vm, port_manager, loop):
|
||||
with asyncio_patch("gns3server.modules.vpcs.vpcs_vm.VPCSVM._check_requirements", return_value=True):
|
||||
with asyncio_patch("asyncio.create_subprocess_exec", return_value=MagicMock()):
|
||||
vm.start()
|
||||
port = vm.console
|
||||
vm.close()
|
||||
loop.run_until_complete(asyncio.async(vm.close()))
|
||||
# Raise an exception if the port is not free
|
||||
port_manager.reserve_console_port(port)
|
||||
assert vm.is_running() is False
|
||||
|
Loading…
Reference in New Issue
Block a user