1
0
mirror of https://github.com/GNS3/gns3-server synced 2024-11-24 17:28:08 +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 os
import sys
import socket import socket
import subprocess import subprocess
import signal
import asyncio import asyncio
import shutil import shutil
from gns3server.utils.asyncio import wait_for_process_termination from gns3server.utils.asyncio import wait_for_process_termination
from gns3server.utils.asyncio import monitor_process from gns3server.utils.asyncio import monitor_process
from gns3server.utils import parse_version
from .traceng_error import TraceNGError from .traceng_error import TraceNGError
from ..adapters.ethernet_adapter import EthernetAdapter from ..adapters.ethernet_adapter import EthernetAdapter
from ..nios.nio_udp import NIOUDP from ..nios.nio_udp import NIOUDP
from ..nios.nio_tap import NIOTAP
from ..base_node import BaseNode from ..base_node import BaseNode
@ -55,12 +51,12 @@ class TraceNGVM(BaseNode):
:param console: TCP console port :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._process = None
self._started = False self._started = False
self._traceng_stdout_file = "" self._ip_address = None
self._local_udp_tunnel = None self._local_udp_tunnel = None
self._ethernet_adapter = EthernetAdapter() # one adapter with 1 Ethernet interface self._ethernet_adapter = EthernetAdapter() # one adapter with 1 Ethernet interface
@ -115,11 +111,12 @@ class TraceNGVM(BaseNode):
def __json__(self): def __json__(self):
return {"name": self.name, return {"name": self.name,
"ip_address": self.ip_address,
"node_id": self.id, "node_id": self.id,
"node_directory": self.working_path, "node_directory": self.working_path,
"status": self.status, "status": self.status,
"console": self._console, "console": self._console,
"console_type": "telnet", "console_type": "none",
"project_id": self.project.id, "project_id": self.project.id,
"command_line": self.command_line} "command_line": self.command_line}
@ -137,8 +134,32 @@ class TraceNGVM(BaseNode):
return search_path return search_path
return 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 @asyncio.coroutine
def start(self): def start(self, destination=None):
""" """
Starts the TraceNG process. Starts the TraceNG process.
""" """
@ -146,11 +167,10 @@ class TraceNGVM(BaseNode):
yield from self._check_requirements() yield from self._check_requirements()
if not self.is_running(): if not self.is_running():
nio = self._ethernet_adapter.get_nio(0) nio = self._ethernet_adapter.get_nio(0)
command = self._build_command() #TODO: validate destination
command = self._build_command(destination)
try: try:
log.info("Starting TraceNG: {}".format(command)) 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 flags = subprocess.CREATE_NEW_CONSOLE
self.command_line = ' '.join(command) self.command_line = ' '.join(command)
self._process = yield from asyncio.create_subprocess_exec(*command, self._process = yield from asyncio.create_subprocess_exec(*command,
@ -230,21 +250,6 @@ class TraceNGVM(BaseNode):
except ProcessLookupError: except ProcessLookupError:
pass 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): def is_running(self):
""" """
Checks if the TraceNG process is running Checks if the TraceNG process is running
@ -373,7 +378,7 @@ class TraceNGVM(BaseNode):
id=self.id, id=self.id,
port_number=port_number)) port_number=port_number))
def _build_command(self): def _build_command(self, destination):
""" """
Command to start the TraceNG process. Command to start the TraceNG process.
(to be passed to subprocess.Popen()) (to be passed to subprocess.Popen())
@ -393,5 +398,5 @@ class TraceNGVM(BaseNode):
except socket.gaierror as e: except socket.gaierror as e:
raise TraceNGError("Can't resolve hostname {}: {}".format(nio.rhost, 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 return command

View File

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

View File

@ -905,6 +905,9 @@ class Project:
""" """
pool = Pool(concurrency=3) pool = Pool(concurrency=3)
for node in self.nodes.values(): 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) pool.append(node.start)
yield from pool.join() yield from pool.join()

View File

@ -25,6 +25,7 @@ from gns3server.compute.traceng import TraceNG
from gns3server.schemas.traceng import ( from gns3server.schemas.traceng import (
TRACENG_CREATE_SCHEMA, TRACENG_CREATE_SCHEMA,
TRACENG_UPDATE_SCHEMA, TRACENG_UPDATE_SCHEMA,
TRACENG_START_SCHEMA,
TRACENG_OBJECT_SCHEMA TRACENG_OBJECT_SCHEMA
) )
@ -54,6 +55,7 @@ class TraceNGHandler:
request.match_info["project_id"], request.match_info["project_id"],
request.json.get("node_id"), request.json.get("node_id"),
console=request.json.get("console")) 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.set_status(201)
response.json(vm) response.json(vm)
@ -96,7 +98,7 @@ class TraceNGHandler:
traceng_manager = TraceNG.instance() traceng_manager = TraceNG.instance()
vm = traceng_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"]) 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.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() vm.updated()
response.json(vm) response.json(vm)
@ -149,12 +151,13 @@ class TraceNGHandler:
404: "Instance doesn't exist" 404: "Instance doesn't exist"
}, },
description="Start a TraceNG instance", description="Start a TraceNG instance",
input=TRACENG_START_SCHEMA,
output=TRACENG_OBJECT_SCHEMA) output=TRACENG_OBJECT_SCHEMA)
def start(request, response): def start(request, response):
traceng_manager = TraceNG.instance() traceng_manager = TraceNG.instance()
vm = traceng_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"]) 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) response.json(vm)
@Route.post( @Route.post(

View File

@ -223,7 +223,7 @@ class NodeHandler:
project = yield from Controller.instance().get_loaded_project(request.match_info["project_id"]) project = yield from Controller.instance().get_loaded_project(request.match_info["project_id"])
node = project.get_node(request.match_info["node_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.json(node)
response.set_status(201) response.set_status(201)

View File

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

View File

@ -43,8 +43,12 @@ TRACENG_CREATE_SCHEMA = {
}, },
"console_type": { "console_type": {
"description": "Console type", "description": "Console type",
"enum": ["telnet"] "enum": ["none"]
}, },
"ip_address": {
"description": "Source IP address for tracing",
"type": ["string"]
}
}, },
"additionalProperties": False, "additionalProperties": False,
"required": ["name"] "required": ["name"]
@ -68,12 +72,29 @@ TRACENG_UPDATE_SCHEMA = {
}, },
"console_type": { "console_type": {
"description": "Console type", "description": "Console type",
"enum": ["telnet"] "enum": ["none"]
}, },
"ip_address": {
"description": "Source IP address for tracing",
"type": ["string"]
}
}, },
"additionalProperties": False, "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 = { TRACENG_OBJECT_SCHEMA = {
"$schema": "http://json-schema.org/draft-04/schema#", "$schema": "http://json-schema.org/draft-04/schema#",
"description": "TraceNG instance", "description": "TraceNG instance",
@ -103,11 +124,11 @@ TRACENG_OBJECT_SCHEMA = {
"description": "Console TCP port", "description": "Console TCP port",
"minimum": 1, "minimum": 1,
"maximum": 65535, "maximum": 65535,
"type": "integer" "type": ["integer", "null"]
}, },
"console_type": { "console_type": {
"description": "Console type", "description": "Console type",
"enum": ["telnet"] "enum": ["none"]
}, },
"project_id": { "project_id": {
"description": "Project UUID", "description": "Project UUID",
@ -119,8 +140,12 @@ TRACENG_OBJECT_SCHEMA = {
"command_line": { "command_line": {
"description": "Last command line used by GNS3 to start TraceNG", "description": "Last command line used by GNS3 to start TraceNG",
"type": "string" "type": "string"
},
"ip_address": {
"description": "Source IP address for tracing",
"type": ["string"]
} }
}, },
"additionalProperties": False, "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("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("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()))
loop.run_until_complete(asyncio.async(vm.start())) assert mock_exec.call_args[0] == (vm._traceng_path(),
assert mock_exec.call_args[0] == (vm._traceng_path(), '-c',
'-p', ANY,
str(vm._internal_console_port), '-v',
'-s', ANY,
ANY, '-b',
'-c', '127.0.0.1',
ANY, '10.0.0.1')
'-t',
'127.0.0.1')
assert vm.is_running() assert vm.is_running()
assert vm.command_line == ' '.join(mock_exec.call_args[0]) assert vm.command_line == ' '.join(mock_exec.call_args[0])
(action, event, kwargs) = async_run(queue.get(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 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._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):
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": {}})
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.port_add_nio_binding(0, nio))
async_run(vm.start()) async_run(vm.start())
assert vm.is_running() assert vm.is_running()
with asyncio_patch("gns3server.utils.asyncio.wait_for_process_termination"): with asyncio_patch("gns3server.utils.asyncio.wait_for_process_termination"):
loop.run_until_complete(asyncio.async(vm.stop())) loop.run_until_complete(asyncio.async(vm.stop()))
assert vm.is_running() is False assert vm.is_running() is False
if sys.platform.startswith("win"): if sys.platform.startswith("win"):
process.send_signal.assert_called_with(1) process.send_signal.assert_called_with(1)
else: else:
process.terminate.assert_called_with() process.terminate.assert_called_with()
async_run(queue.get(0)) #  Ping async_run(queue.get(0)) #  Ping
async_run(queue.get(0)) #  Started async_run(queue.get(0)) #  Started
(action, event, kwargs) = async_run(queue.get(0)) (action, event, kwargs) = async_run(queue.get(0))
assert action == "node.updated" assert action == "node.updated"
assert event == vm assert event == vm
def test_reload(loop, vm, async_run): def test_reload(loop, vm, async_run):
@ -135,22 +132,21 @@ def test_reload(loop, vm, async_run):
process.returncode = None 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._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):
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": {}})
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.port_add_nio_binding(0, nio)) async_run(vm.start())
async_run(vm.start()) assert vm.is_running()
assert vm.is_running()
vm._ubridge_send = AsyncioMagicMock() vm._ubridge_send = AsyncioMagicMock()
with asyncio_patch("gns3server.utils.asyncio.wait_for_process_termination"): with asyncio_patch("gns3server.utils.asyncio.wait_for_process_termination"):
async_run(vm.reload()) async_run(vm.reload())
assert vm.is_running() is True assert vm.is_running() is True
#if sys.platform.startswith("win"): #if sys.platform.startswith("win"):
# process.send_signal.assert_called_with(1) # process.send_signal.assert_called_with(1)
#else: #else:
process.terminate.assert_called_with() process.terminate.assert_called_with()
def test_add_nio_binding_udp(vm, async_run): 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): def test_get(http_compute, windows_platform):
response = http_compute.get('/capabilities', example=True) response = http_compute.get('/capabilities', example=True)
assert response.status == 200 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") @pytest.mark.skipif(sys.platform.startswith("win"), reason="Not supported on Windows")
def test_get_on_gns3vm(http_compute, on_gns3vm): def test_get_on_gns3vm(http_compute, on_gns3vm):
response = http_compute.get('/capabilities', example=True) response = http_compute.get('/capabilities', example=True)
assert response.status == 200 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" 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): 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}) 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 assert response.status == 201