mirror of
https://github.com/GNS3/gns3-server.git
synced 2024-12-22 14:12:29 +00:00
Merge branch '2.3'
# Conflicts: # gns3server/version.py
This commit is contained in:
commit
2606c2d9a7
@ -27,6 +27,7 @@ import psutil
|
|||||||
import platform
|
import platform
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
from aiohttp.web import WebSocketResponse
|
||||||
from gns3server.utils.interfaces import interfaces
|
from gns3server.utils.interfaces import interfaces
|
||||||
from ..compute.port_manager import PortManager
|
from ..compute.port_manager import PortManager
|
||||||
from ..utils.asyncio import wait_run_in_executor, locking
|
from ..utils.asyncio import wait_run_in_executor, locking
|
||||||
@ -339,8 +340,8 @@ class BaseNode:
|
|||||||
|
|
||||||
async def start_wrap_console(self):
|
async def start_wrap_console(self):
|
||||||
"""
|
"""
|
||||||
Start a telnet proxy for the console allowing multiple client
|
Start a telnet proxy for the console allowing multiple telnet clients
|
||||||
connected at the same time
|
to be connected at the same time
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not self._wrap_console or self._console_type != "telnet":
|
if not self._wrap_console or self._console_type != "telnet":
|
||||||
@ -369,6 +370,62 @@ class BaseNode:
|
|||||||
self._wrapper_telnet_server.close()
|
self._wrapper_telnet_server.close()
|
||||||
await self._wrapper_telnet_server.wait_closed()
|
await self._wrapper_telnet_server.wait_closed()
|
||||||
|
|
||||||
|
async def start_websocket_console(self, request):
|
||||||
|
"""
|
||||||
|
Connect to console using Websocket.
|
||||||
|
|
||||||
|
:param ws: Websocket object
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self.status != "started":
|
||||||
|
raise NodeError("Node {} is not started".format(self.name))
|
||||||
|
|
||||||
|
if self._console_type != "telnet":
|
||||||
|
raise NodeError("Node {} console type is not telnet".format(self.name))
|
||||||
|
|
||||||
|
try:
|
||||||
|
(telnet_reader, telnet_writer) = await asyncio.open_connection(self._manager.port_manager.console_host, self.console)
|
||||||
|
except ConnectionError as e:
|
||||||
|
raise NodeError("Cannot connect to node {} telnet server: {}".format(self.name, e))
|
||||||
|
|
||||||
|
log.info("Connected to Telnet server")
|
||||||
|
|
||||||
|
ws = WebSocketResponse()
|
||||||
|
await ws.prepare(request)
|
||||||
|
request.app['websockets'].add(ws)
|
||||||
|
|
||||||
|
log.info("New client has connected to console WebSocket")
|
||||||
|
|
||||||
|
async def ws_forward(telnet_writer):
|
||||||
|
|
||||||
|
async for msg in ws:
|
||||||
|
if msg.type == aiohttp.WSMsgType.TEXT:
|
||||||
|
telnet_writer.write(msg.data.encode())
|
||||||
|
await telnet_writer.drain()
|
||||||
|
elif msg.type == aiohttp.WSMsgType.BINARY:
|
||||||
|
await telnet_writer.write(msg.data)
|
||||||
|
await telnet_writer.drain()
|
||||||
|
elif msg.type == aiohttp.WSMsgType.ERROR:
|
||||||
|
log.debug("Websocket connection closed with exception {}".format(ws.exception()))
|
||||||
|
|
||||||
|
async def telnet_forward(telnet_reader):
|
||||||
|
|
||||||
|
while not ws.closed and not telnet_reader.at_eof():
|
||||||
|
data = await telnet_reader.read(1024)
|
||||||
|
if data:
|
||||||
|
await ws.send_bytes(data)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# keep forwarding websocket data in both direction
|
||||||
|
await asyncio.wait([ws_forward(telnet_writer), telnet_forward(telnet_reader)], return_when=asyncio.FIRST_COMPLETED)
|
||||||
|
finally:
|
||||||
|
log.info("Client has disconnected from console WebSocket")
|
||||||
|
if not ws.closed:
|
||||||
|
await ws.close()
|
||||||
|
request.app['websockets'].discard(ws)
|
||||||
|
|
||||||
|
return ws
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def allocate_aux(self):
|
def allocate_aux(self):
|
||||||
"""
|
"""
|
||||||
|
@ -412,3 +412,16 @@ class DockerHandler:
|
|||||||
docker_manager = Docker.instance()
|
docker_manager = Docker.instance()
|
||||||
images = await docker_manager.list_images()
|
images = await docker_manager.list_images()
|
||||||
response.json(images)
|
response.json(images)
|
||||||
|
|
||||||
|
@Route.get(
|
||||||
|
r"/projects/{project_id}/docker/nodes/{node_id}/console/ws",
|
||||||
|
description="WebSocket for console",
|
||||||
|
parameters={
|
||||||
|
"project_id": "Project UUID",
|
||||||
|
"node_id": "Node UUID",
|
||||||
|
})
|
||||||
|
async def console_ws(request, response):
|
||||||
|
|
||||||
|
docker_manager = Docker.instance()
|
||||||
|
container = docker_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||||
|
return await container.start_websocket_console(request)
|
||||||
|
@ -513,3 +513,15 @@ class DynamipsVMHandler:
|
|||||||
response.set_status(201)
|
response.set_status(201)
|
||||||
response.json(new_node)
|
response.json(new_node)
|
||||||
|
|
||||||
|
@Route.get(
|
||||||
|
r"/projects/{project_id}/dynamips/nodes/{node_id}/console/ws",
|
||||||
|
description="WebSocket for console",
|
||||||
|
parameters={
|
||||||
|
"project_id": "Project UUID",
|
||||||
|
"node_id": "Node UUID",
|
||||||
|
})
|
||||||
|
async def console_ws(request, response):
|
||||||
|
|
||||||
|
dynamips_manager = Dynamips.instance()
|
||||||
|
vm = dynamips_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||||
|
return await vm.start_websocket_console(request)
|
||||||
|
@ -453,3 +453,16 @@ class IOUHandler:
|
|||||||
raise aiohttp.web.HTTPForbidden()
|
raise aiohttp.web.HTTPForbidden()
|
||||||
|
|
||||||
await response.stream_file(image_path)
|
await response.stream_file(image_path)
|
||||||
|
|
||||||
|
@Route.get(
|
||||||
|
r"/projects/{project_id}/iou/nodes/{node_id}/console/ws",
|
||||||
|
description="WebSocket for console",
|
||||||
|
parameters={
|
||||||
|
"project_id": "Project UUID",
|
||||||
|
"node_id": "Node UUID",
|
||||||
|
})
|
||||||
|
async def console_ws(request, response):
|
||||||
|
|
||||||
|
iou_manager = IOU.instance()
|
||||||
|
vm = iou_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||||
|
return await vm.start_websocket_console(request)
|
||||||
|
@ -580,3 +580,16 @@ class QEMUHandler:
|
|||||||
raise aiohttp.web.HTTPForbidden()
|
raise aiohttp.web.HTTPForbidden()
|
||||||
|
|
||||||
await response.stream_file(image_path)
|
await response.stream_file(image_path)
|
||||||
|
|
||||||
|
@Route.get(
|
||||||
|
r"/projects/{project_id}/qemu/nodes/{node_id}/console/ws",
|
||||||
|
description="WebSocket for console",
|
||||||
|
parameters={
|
||||||
|
"project_id": "Project UUID",
|
||||||
|
"node_id": "Node UUID",
|
||||||
|
})
|
||||||
|
async def console_ws(request, response):
|
||||||
|
|
||||||
|
qemu_manager = Qemu.instance()
|
||||||
|
vm = qemu_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||||
|
return await vm.start_websocket_console(request)
|
||||||
|
@ -424,3 +424,16 @@ class VirtualBoxHandler:
|
|||||||
vbox_manager = VirtualBox.instance()
|
vbox_manager = VirtualBox.instance()
|
||||||
vms = await vbox_manager.list_vms()
|
vms = await vbox_manager.list_vms()
|
||||||
response.json(vms)
|
response.json(vms)
|
||||||
|
|
||||||
|
@Route.get(
|
||||||
|
r"/projects/{project_id}/virtualbox/nodes/{node_id}/console/ws",
|
||||||
|
description="WebSocket for console",
|
||||||
|
parameters={
|
||||||
|
"project_id": "Project UUID",
|
||||||
|
"node_id": "Node UUID",
|
||||||
|
})
|
||||||
|
async def console_ws(request, response):
|
||||||
|
|
||||||
|
virtualbox_manager = VirtualBox.instance()
|
||||||
|
vm = virtualbox_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||||
|
return await vm.start_websocket_console(request)
|
||||||
|
@ -409,3 +409,16 @@ class VMwareHandler:
|
|||||||
vmware_manager = VMware.instance()
|
vmware_manager = VMware.instance()
|
||||||
vms = await vmware_manager.list_vms()
|
vms = await vmware_manager.list_vms()
|
||||||
response.json(vms)
|
response.json(vms)
|
||||||
|
|
||||||
|
@Route.get(
|
||||||
|
r"/projects/{project_id}/vmware/nodes/{node_id}/console/ws",
|
||||||
|
description="WebSocket for console",
|
||||||
|
parameters={
|
||||||
|
"project_id": "Project UUID",
|
||||||
|
"node_id": "Node UUID",
|
||||||
|
})
|
||||||
|
async def console_ws(request, response):
|
||||||
|
|
||||||
|
vmware_manager = VMware.instance()
|
||||||
|
vm = vmware_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||||
|
return await vm.start_websocket_console(request)
|
||||||
|
@ -362,3 +362,16 @@ class VPCSHandler:
|
|||||||
port_number = int(request.match_info["port_number"])
|
port_number = int(request.match_info["port_number"])
|
||||||
nio = vm.get_nio(port_number)
|
nio = vm.get_nio(port_number)
|
||||||
await vpcs_manager.stream_pcap_file(nio, vm.project.id, request, response)
|
await vpcs_manager.stream_pcap_file(nio, vm.project.id, request, response)
|
||||||
|
|
||||||
|
@Route.get(
|
||||||
|
r"/projects/{project_id}/vpcs/nodes/{node_id}/console/ws",
|
||||||
|
description="WebSocket for console",
|
||||||
|
parameters={
|
||||||
|
"project_id": "Project UUID",
|
||||||
|
"node_id": "Node UUID",
|
||||||
|
})
|
||||||
|
async def console_ws(request, response):
|
||||||
|
|
||||||
|
vpcs_manager = VPCS.instance()
|
||||||
|
vm = vpcs_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||||
|
return await vm.start_websocket_console(request)
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
|
import asyncio
|
||||||
|
|
||||||
from gns3server.web.route import Route
|
from gns3server.web.route import Route
|
||||||
from gns3server.controller import Controller
|
from gns3server.controller import Controller
|
||||||
@ -453,3 +454,57 @@ class NodeHandler:
|
|||||||
data = await request.content.read() #FIXME: are we handling timeout or large files correctly?
|
data = await request.content.read() #FIXME: are we handling timeout or large files correctly?
|
||||||
await node.compute.http_query("POST", "/projects/{project_id}/files{path}".format(project_id=project.id, path=path), data=data, timeout=None, raw=True)
|
await node.compute.http_query("POST", "/projects/{project_id}/files{path}".format(project_id=project.id, path=path), data=data, timeout=None, raw=True)
|
||||||
response.set_status(201)
|
response.set_status(201)
|
||||||
|
|
||||||
|
@Route.get(
|
||||||
|
r"/projects/{project_id}/nodes/{node_id}/console/ws",
|
||||||
|
parameters={
|
||||||
|
"project_id": "Project UUID",
|
||||||
|
"node_id": "Node UUID"
|
||||||
|
},
|
||||||
|
description="Connect to WebSocket console",
|
||||||
|
status_codes={
|
||||||
|
200: "File returned",
|
||||||
|
403: "Permission denied",
|
||||||
|
404: "The file doesn't exist"
|
||||||
|
})
|
||||||
|
async def ws_console(request, response):
|
||||||
|
|
||||||
|
project = await Controller.instance().get_loaded_project(request.match_info["project_id"])
|
||||||
|
node = project.get_node(request.match_info["node_id"])
|
||||||
|
compute = node.compute
|
||||||
|
ws = aiohttp.web.WebSocketResponse()
|
||||||
|
await ws.prepare(request)
|
||||||
|
request.app['websockets'].add(ws)
|
||||||
|
|
||||||
|
ws_console_compute_url = "ws://{compute_host}:{compute_port}/v2/compute/projects/{project_id}/{node_type}/nodes/{node_id}/console/ws".format(compute_host=compute.host,
|
||||||
|
compute_port=compute.port,
|
||||||
|
project_id=project.id,
|
||||||
|
node_type=node.node_type,
|
||||||
|
node_id=node.id)
|
||||||
|
|
||||||
|
async def ws_forward(ws_client):
|
||||||
|
async for msg in ws:
|
||||||
|
if msg.type == aiohttp.WSMsgType.TEXT:
|
||||||
|
await ws_client.send_str(msg.data)
|
||||||
|
elif msg.type == aiohttp.WSMsgType.BINARY:
|
||||||
|
await ws_client.send_bytes(msg.data)
|
||||||
|
elif msg.type == aiohttp.WSMsgType.ERROR:
|
||||||
|
break
|
||||||
|
|
||||||
|
try:
|
||||||
|
async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(limit=None, force_close=True)) as session:
|
||||||
|
async with session.ws_connect(ws_console_compute_url) as ws_client:
|
||||||
|
asyncio.ensure_future(ws_forward(ws_client))
|
||||||
|
async for msg in ws_client:
|
||||||
|
if msg.type == aiohttp.WSMsgType.TEXT:
|
||||||
|
await ws.send_str(msg.data)
|
||||||
|
elif msg.type == aiohttp.WSMsgType.BINARY:
|
||||||
|
await ws.send_bytes(msg.data)
|
||||||
|
elif msg.type == aiohttp.WSMsgType.ERROR:
|
||||||
|
break
|
||||||
|
finally:
|
||||||
|
if not ws.closed:
|
||||||
|
await ws.close()
|
||||||
|
request.app['websockets'].discard(ws)
|
||||||
|
|
||||||
|
return ws
|
||||||
|
@ -106,7 +106,8 @@ class Route(object):
|
|||||||
:returns: Response if you need to auth the user otherwise None
|
:returns: Response if you need to auth the user otherwise None
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not server_config.getboolean("auth", False):
|
# FIXME: ugly exception to not require authentication for websocket consoles
|
||||||
|
if not server_config.getboolean("auth", False) or request.path.endswith("console/ws"):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
user = server_config.get("user", "").strip()
|
user = server_config.get("user", "").strip()
|
||||||
@ -253,10 +254,11 @@ class Route(object):
|
|||||||
"""
|
"""
|
||||||
To avoid strange effect we prevent concurrency
|
To avoid strange effect we prevent concurrency
|
||||||
between the same instance of the node
|
between the same instance of the node
|
||||||
(excepting when streaming a PCAP file).
|
(excepting when streaming a PCAP file and WebSocket consoles).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if "node_id" in request.match_info and not "pcap" in request.path:
|
#FIXME: ugly exceptions for capture and websocket console
|
||||||
|
if "node_id" in request.match_info and not "pcap" in request.path and not request.path.endswith("console/ws"):
|
||||||
node_id = request.match_info.get("node_id")
|
node_id = request.match_info.get("node_id")
|
||||||
|
|
||||||
if "compute" in request.path:
|
if "compute" in request.path:
|
||||||
|
Loading…
Reference in New Issue
Block a user