mirror of
https://github.com/GNS3/gns3-server
synced 2024-12-25 00:08:11 +00:00
Support for source and destination for traceNG.
This commit is contained in:
parent
8e695c8af1
commit
d08c08617c
@ -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
|
||||
|
@ -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))
|
||||
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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(
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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",
|
||||
|
@ -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"]
|
||||
}
|
||||
|
@ -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):
|
||||
|
@ -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}
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user