Merge remote-tracking branch 'origin/asyncio' into asyncio

This commit is contained in:
Jeremy 2015-02-16 16:53:56 -07:00
commit 516b882122
7 changed files with 221 additions and 31 deletions

View File

@ -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)

View File

@ -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,21 +742,21 @@ 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,
adapter_number=adapter_number))
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,
@ -772,19 +772,19 @@ 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,
adapter_number=adapter_number))
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,
@ -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)

View File

@ -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

View File

@ -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"]
}

View File

@ -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

View File

@ -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

View File

@ -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