1
0
mirror of https://github.com/GNS3/gns3-server synced 2024-11-12 19:38:57 +00:00

Support for source and destination for traceNG.

This commit is contained in:
grossmj 2018-03-27 16:58:49 +07:00
parent 8e695c8af1
commit d08c08617c
10 changed files with 117 additions and 93 deletions

View File

@ -20,21 +20,17 @@ TraceNG VM management in order to run a TraceNG VM.
"""
import os
import sys
import socket
import subprocess
import signal
import asyncio
import shutil
from gns3server.utils.asyncio import wait_for_process_termination
from gns3server.utils.asyncio import monitor_process
from gns3server.utils import parse_version
from .traceng_error import TraceNGError
from ..adapters.ethernet_adapter import EthernetAdapter
from ..nios.nio_udp import NIOUDP
from ..nios.nio_tap import NIOTAP
from ..base_node import BaseNode
@ -55,12 +51,12 @@ class TraceNGVM(BaseNode):
:param console: TCP console port
"""
def __init__(self, name, node_id, project, manager, console=None):
def __init__(self, name, node_id, project, manager, console=None, console_type="none"):
super().__init__(name, node_id, project, manager, console=console)
super().__init__(name, node_id, project, manager, console=console, console_type=console_type)
self._process = None
self._started = False
self._traceng_stdout_file = ""
self._ip_address = None
self._local_udp_tunnel = None
self._ethernet_adapter = EthernetAdapter() # one adapter with 1 Ethernet interface
@ -115,11 +111,12 @@ class TraceNGVM(BaseNode):
def __json__(self):
return {"name": self.name,
"ip_address": self.ip_address,
"node_id": self.id,
"node_directory": self.working_path,
"status": self.status,
"console": self._console,
"console_type": "telnet",
"console_type": "none",
"project_id": self.project.id,
"command_line": self.command_line}
@ -137,8 +134,32 @@ class TraceNGVM(BaseNode):
return search_path
return path
@property
def ip_address(self):
"""
Returns the IP address for this node.
:returns: IP address
"""
return self._ip_address
@ip_address.setter
def ip_address(self, ip_address):
"""
Sets the IP address of this node.
:param ip_address: IP address
"""
log.info("{module}: {name} [{id}] set IP address to {ip_address}".format(module=self.manager.module_name,
name=self.name,
id=self.id,
ip_address=ip_address))
self._ip_address = ip_address
@asyncio.coroutine
def start(self):
def start(self, destination=None):
"""
Starts the TraceNG process.
"""
@ -146,11 +167,10 @@ class TraceNGVM(BaseNode):
yield from self._check_requirements()
if not self.is_running():
nio = self._ethernet_adapter.get_nio(0)
command = self._build_command()
#TODO: validate destination
command = self._build_command(destination)
try:
log.info("Starting TraceNG: {}".format(command))
self._traceng_stdout_file = os.path.join(self.working_dir, "traceng.log")
log.info("Logging to {}".format(self._traceng_stdout_file))
flags = subprocess.CREATE_NEW_CONSOLE
self.command_line = ' '.join(command)
self._process = yield from asyncio.create_subprocess_exec(*command,
@ -230,21 +250,6 @@ class TraceNGVM(BaseNode):
except ProcessLookupError:
pass
def read_traceng_stdout(self):
"""
Reads the standard output of the TraceNG process.
Only use when the process has been stopped or has crashed.
"""
output = ""
if self._traceng_stdout_file:
try:
with open(self._traceng_stdout_file, "rb") as file:
output = file.read().decode("utf-8", errors="replace")
except OSError as e:
log.warning("Could not read {}: {}".format(self._traceng_stdout_file, e))
return output
def is_running(self):
"""
Checks if the TraceNG process is running
@ -373,7 +378,7 @@ class TraceNGVM(BaseNode):
id=self.id,
port_number=port_number))
def _build_command(self):
def _build_command(self, destination):
"""
Command to start the TraceNG process.
(to be passed to subprocess.Popen())
@ -393,5 +398,5 @@ class TraceNGVM(BaseNode):
except socket.gaierror as e:
raise TraceNGError("Can't resolve hostname {}: {}".format(nio.rhost, e))
#command.extend(["10.0.0.1"]) # TODO: remove
command.extend([destination]) # host or IP to trace
return command

View File

@ -454,7 +454,7 @@ class Node:
yield from self.delete()
@asyncio.coroutine
def start(self):
def start(self, data=None):
"""
Start a node
"""
@ -467,7 +467,7 @@ class Node:
raise aiohttp.web.HTTPConflict(text="IOU licence is not configured")
yield from self.post("/start", timeout=240, data={"iourc_content": licence})
else:
yield from self.post("/start", timeout=240)
yield from self.post("/start", data=data, timeout=240)
except asyncio.TimeoutError:
raise aiohttp.web.HTTPRequestTimeout(text="Timeout when starting {}".format(self._name))

View File

@ -905,6 +905,9 @@ class Project:
"""
pool = Pool(concurrency=3)
for node in self.nodes.values():
if node.node_type == "traceng":
#FIXME: maybe not the right place to do this...
continue
pool.append(node.start)
yield from pool.join()

View File

@ -25,6 +25,7 @@ from gns3server.compute.traceng import TraceNG
from gns3server.schemas.traceng import (
TRACENG_CREATE_SCHEMA,
TRACENG_UPDATE_SCHEMA,
TRACENG_START_SCHEMA,
TRACENG_OBJECT_SCHEMA
)
@ -54,6 +55,7 @@ class TraceNGHandler:
request.match_info["project_id"],
request.json.get("node_id"),
console=request.json.get("console"))
vm.ip_address = request.json.get("ip_address", "") # FIXME, required IP address to create node?
response.set_status(201)
response.json(vm)
@ -96,7 +98,7 @@ class TraceNGHandler:
traceng_manager = TraceNG.instance()
vm = traceng_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
vm.name = request.json.get("name", vm.name)
vm.console = request.json.get("console", vm.console)
vm.ip_address = request.json.get("ip_address", vm.ip_address)
vm.updated()
response.json(vm)
@ -149,12 +151,13 @@ class TraceNGHandler:
404: "Instance doesn't exist"
},
description="Start a TraceNG instance",
input=TRACENG_START_SCHEMA,
output=TRACENG_OBJECT_SCHEMA)
def start(request, response):
traceng_manager = TraceNG.instance()
vm = traceng_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
yield from vm.start()
yield from vm.start(request.json["destination"])
response.json(vm)
@Route.post(

View File

@ -223,7 +223,7 @@ class NodeHandler:
project = yield from Controller.instance().get_loaded_project(request.match_info["project_id"])
node = project.get_node(request.match_info["node_id"])
yield from node.start()
yield from node.start(data=request.json)
response.json(node)
response.set_status(201)

View File

@ -145,7 +145,7 @@ NODE_OBJECT_SCHEMA = {
},
"console_type": {
"description": "Console type",
"enum": ["vnc", "telnet", "http", "https", "spice", None]
"enum": ["vnc", "telnet", "http", "https", "spice", "none", None]
},
"properties": {
"description": "Properties specific to an emulator",

View File

@ -43,8 +43,12 @@ TRACENG_CREATE_SCHEMA = {
},
"console_type": {
"description": "Console type",
"enum": ["telnet"]
"enum": ["none"]
},
"ip_address": {
"description": "Source IP address for tracing",
"type": ["string"]
}
},
"additionalProperties": False,
"required": ["name"]
@ -68,12 +72,29 @@ TRACENG_UPDATE_SCHEMA = {
},
"console_type": {
"description": "Console type",
"enum": ["telnet"]
"enum": ["none"]
},
"ip_address": {
"description": "Source IP address for tracing",
"type": ["string"]
}
},
"additionalProperties": False,
}
TRACENG_START_SCHEMA = {
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Request validation to start a TraceNG instance",
"type": "object",
"properties": {
"destination": {
"description": "Host or IP address to trace",
"type": ["string"]
}
},
"required": ["destination"],
}
TRACENG_OBJECT_SCHEMA = {
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "TraceNG instance",
@ -103,11 +124,11 @@ TRACENG_OBJECT_SCHEMA = {
"description": "Console TCP port",
"minimum": 1,
"maximum": 65535,
"type": "integer"
"type": ["integer", "null"]
},
"console_type": {
"description": "Console type",
"enum": ["telnet"]
"enum": ["none"]
},
"project_id": {
"description": "Project UUID",
@ -119,8 +140,12 @@ TRACENG_OBJECT_SCHEMA = {
"command_line": {
"description": "Last command line used by GNS3 to start TraceNG",
"type": "string"
},
"ip_address": {
"description": "Source IP address for tracing",
"type": ["string"]
}
},
"additionalProperties": False,
"required": ["name", "node_id", "status", "console", "console_type", "project_id", "command_line"]
"required": ["name", "node_id", "status", "console", "console_type", "project_id", "command_line", "ip_address"]
}

View File

@ -71,17 +71,15 @@ def test_start(loop, vm, async_run):
with asyncio_patch("gns3server.compute.traceng.traceng_vm.TraceNGVM._check_requirements", return_value=True):
with asyncio_patch("asyncio.create_subprocess_exec", return_value=process) as mock_exec:
with asyncio_patch("gns3server.compute.traceng.traceng_vm.TraceNGVM.start_wrap_console"):
loop.run_until_complete(asyncio.async(vm.start()))
assert mock_exec.call_args[0] == (vm._traceng_path(),
'-p',
str(vm._internal_console_port),
'-s',
ANY,
'-c',
ANY,
'-t',
'127.0.0.1')
loop.run_until_complete(asyncio.async(vm.start()))
assert mock_exec.call_args[0] == (vm._traceng_path(),
'-c',
ANY,
'-v',
ANY,
'-b',
'127.0.0.1',
'10.0.0.1')
assert vm.is_running()
assert vm.command_line == ' '.join(mock_exec.call_args[0])
(action, event, kwargs) = async_run(queue.get(0))
@ -100,29 +98,28 @@ def test_stop(loop, vm, async_run):
with NotificationManager.instance().queue() as queue:
with asyncio_patch("gns3server.compute.traceng.traceng_vm.TraceNGVM._check_requirements", return_value=True):
with asyncio_patch("gns3server.compute.traceng.traceng_vm.TraceNGVM.start_wrap_console"):
with asyncio_patch("asyncio.create_subprocess_exec", return_value=process):
nio = TraceNG.instance().create_nio({"type": "nio_udp", "lport": 4242, "rport": 4243, "rhost": "127.0.0.1", "filters": {}})
async_run(vm.port_add_nio_binding(0, nio))
with asyncio_patch("asyncio.create_subprocess_exec", return_value=process):
nio = TraceNG.instance().create_nio({"type": "nio_udp", "lport": 4242, "rport": 4243, "rhost": "127.0.0.1", "filters": {}})
async_run(vm.port_add_nio_binding(0, nio))
async_run(vm.start())
assert vm.is_running()
async_run(vm.start())
assert vm.is_running()
with asyncio_patch("gns3server.utils.asyncio.wait_for_process_termination"):
loop.run_until_complete(asyncio.async(vm.stop()))
assert vm.is_running() is False
with asyncio_patch("gns3server.utils.asyncio.wait_for_process_termination"):
loop.run_until_complete(asyncio.async(vm.stop()))
assert vm.is_running() is False
if sys.platform.startswith("win"):
process.send_signal.assert_called_with(1)
else:
process.terminate.assert_called_with()
if sys.platform.startswith("win"):
process.send_signal.assert_called_with(1)
else:
process.terminate.assert_called_with()
async_run(queue.get(0)) #  Ping
async_run(queue.get(0)) #  Started
async_run(queue.get(0)) #  Ping
async_run(queue.get(0)) #  Started
(action, event, kwargs) = async_run(queue.get(0))
assert action == "node.updated"
assert event == vm
(action, event, kwargs) = async_run(queue.get(0))
assert action == "node.updated"
assert event == vm
def test_reload(loop, vm, async_run):
@ -135,22 +132,21 @@ def test_reload(loop, vm, async_run):
process.returncode = None
with asyncio_patch("gns3server.compute.traceng.traceng_vm.TraceNGVM._check_requirements", return_value=True):
with asyncio_patch("gns3server.compute.traceng.traceng_vm.TraceNGVM.start_wrap_console"):
with asyncio_patch("asyncio.create_subprocess_exec", return_value=process):
nio = TraceNG.instance().create_nio({"type": "nio_udp", "lport": 4242, "rport": 4243, "rhost": "127.0.0.1", "filters": {}})
async_run(vm.port_add_nio_binding(0, nio))
async_run(vm.start())
assert vm.is_running()
with asyncio_patch("asyncio.create_subprocess_exec", return_value=process):
nio = TraceNG.instance().create_nio({"type": "nio_udp", "lport": 4242, "rport": 4243, "rhost": "127.0.0.1", "filters": {}})
async_run(vm.port_add_nio_binding(0, nio))
async_run(vm.start())
assert vm.is_running()
vm._ubridge_send = AsyncioMagicMock()
with asyncio_patch("gns3server.utils.asyncio.wait_for_process_termination"):
async_run(vm.reload())
assert vm.is_running() is True
vm._ubridge_send = AsyncioMagicMock()
with asyncio_patch("gns3server.utils.asyncio.wait_for_process_termination"):
async_run(vm.reload())
assert vm.is_running() is True
#if sys.platform.startswith("win"):
# process.send_signal.assert_called_with(1)
#else:
process.terminate.assert_called_with()
#if sys.platform.startswith("win"):
# process.send_signal.assert_called_with(1)
#else:
process.terminate.assert_called_with()
def test_add_nio_binding_udp(vm, async_run):

View File

@ -31,11 +31,11 @@ from gns3server.version import __version__
def test_get(http_compute, windows_platform):
response = http_compute.get('/capabilities', example=True)
assert response.status == 200
assert response.json == {'node_types': ['cloud', 'ethernet_hub', 'ethernet_switch', 'nat', 'vpcs', 'virtualbox', 'dynamips', 'frame_relay_switch', 'atm_switch', 'qemu', 'vmware', 'docker', 'iou'], 'version': __version__, 'platform': sys.platform}
assert response.json == {'node_types': ['cloud', 'ethernet_hub', 'ethernet_switch', 'nat', 'vpcs', 'virtualbox', 'dynamips', 'frame_relay_switch', 'atm_switch', 'qemu', 'vmware', 'docker', 'iou', 'traceng'], 'version': __version__, 'platform': sys.platform}
@pytest.mark.skipif(sys.platform.startswith("win"), reason="Not supported on Windows")
def test_get_on_gns3vm(http_compute, on_gns3vm):
response = http_compute.get('/capabilities', example=True)
assert response.status == 200
assert response.json == {'node_types': ['cloud', 'ethernet_hub', 'ethernet_switch', 'nat', 'vpcs', 'virtualbox', 'dynamips', 'frame_relay_switch', 'atm_switch', 'qemu', 'vmware', 'docker', 'iou'], 'version': __version__, 'platform': sys.platform}
assert response.json == {'node_types': ['cloud', 'ethernet_hub', 'ethernet_switch', 'nat', 'vpcs', 'virtualbox', 'dynamips', 'frame_relay_switch', 'atm_switch', 'qemu', 'vmware', 'docker', 'iou', 'traceng'], 'version': __version__, 'platform': sys.platform}

View File

@ -47,14 +47,6 @@ def test_traceng_get(http_compute, project, vm):
assert response.json["status"] == "stopped"
def test_traceng_create_startup_script(http_compute, project):
response = http_compute.post("/projects/{project_id}/traceng/nodes".format(project_id=project.id), {"name": "TraceNG TEST 1", "startup_script": "ip 192.168.1.2\necho TEST"})
assert response.status == 201
assert response.route == "/projects/{project_id}/traceng/nodes"
assert response.json["name"] == "TraceNG TEST 1"
assert response.json["project_id"] == project.id
def test_traceng_create_port(http_compute, project, free_console_port):
response = http_compute.post("/projects/{project_id}/traceng/nodes".format(project_id=project.id), {"name": "TraceNG TEST 1", "console": free_console_port})
assert response.status == 201