mirror of
https://github.com/GNS3/gns3-server
synced 2024-12-02 21:28:10 +00:00
Functional cloud. Fixes #402.
This commit is contained in:
parent
39a3f2fae2
commit
59f22cd346
@ -415,6 +415,18 @@ class BaseNode:
|
|||||||
raise NodeError("uBridge is not installed")
|
raise NodeError("uBridge is not installed")
|
||||||
return path
|
return path
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def _ubridge_send(self, command):
|
||||||
|
"""
|
||||||
|
Sends a command to uBridge hypervisor.
|
||||||
|
|
||||||
|
:param command: command to send
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not self._ubridge_hypervisor or not self._ubridge_hypervisor.is_running():
|
||||||
|
raise NodeError("Cannot send command '{}': uBridge is not running".format(command))
|
||||||
|
yield from self._ubridge_hypervisor.send(command)
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def _start_ubridge(self):
|
def _start_ubridge(self):
|
||||||
"""
|
"""
|
||||||
@ -433,6 +445,15 @@ class BaseNode:
|
|||||||
log.info("Hypervisor {}:{} has successfully started".format(self._ubridge_hypervisor.host, self._ubridge_hypervisor.port))
|
log.info("Hypervisor {}:{} has successfully started".format(self._ubridge_hypervisor.host, self._ubridge_hypervisor.port))
|
||||||
yield from self._ubridge_hypervisor.connect()
|
yield from self._ubridge_hypervisor.connect()
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def _stop_ubridge(self):
|
||||||
|
"""
|
||||||
|
Stops uBridge.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self._ubridge_hypervisor and self._ubridge_hypervisor.is_running():
|
||||||
|
yield from self._ubridge_hypervisor.stop()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def hw_virtualization(self):
|
def hw_virtualization(self):
|
||||||
"""
|
"""
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# 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 sys
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
from ...node_error import NodeError
|
from ...node_error import NodeError
|
||||||
@ -62,7 +63,8 @@ class Cloud(BaseNode):
|
|||||||
"node_id": self.id,
|
"node_id": self.id,
|
||||||
"project_id": self.project.id,
|
"project_id": self.project.id,
|
||||||
"ports": self._ports,
|
"ports": self._ports,
|
||||||
"interfaces": host_interfaces}
|
"interfaces": host_interfaces,
|
||||||
|
"status": "started"}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ports(self):
|
def ports(self):
|
||||||
@ -84,25 +86,103 @@ class Cloud(BaseNode):
|
|||||||
|
|
||||||
self._ports = ports
|
self._ports = ports
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
def create(self):
|
def create(self):
|
||||||
"""
|
"""
|
||||||
Creates this cloud.
|
Creates this cloud.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
super().create()
|
yield from self._start_ubridge()
|
||||||
log.info('Cloud "{name}" [{id}] has been created'.format(name=self._name, id=self._id))
|
log.info('Cloud "{name}" [{id}] has been created'.format(name=self._name, id=self._id))
|
||||||
|
|
||||||
def delete(self):
|
@asyncio.coroutine
|
||||||
|
def close(self):
|
||||||
"""
|
"""
|
||||||
Deletes this cloud.
|
Closes this cloud.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if not (yield from super().close()):
|
||||||
|
return False
|
||||||
|
|
||||||
for nio in self._nios.values():
|
for nio in self._nios.values():
|
||||||
if nio and isinstance(nio, NIOUDP):
|
if nio and isinstance(nio, NIOUDP):
|
||||||
self.manager.port_manager.release_udp_port(nio.lport, self._project)
|
self.manager.port_manager.release_udp_port(nio.lport, self._project)
|
||||||
|
|
||||||
super().delete()
|
yield from self._stop_ubridge()
|
||||||
log.info('Cloud "{name}" [{id}] has been deleted'.format(name=self._name, id=self._id))
|
log.info('Cloud "{name}" [{id}] has been closed'.format(name=self._name, id=self._id))
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def _add_ubridge_connection(self, nio, port_number):
|
||||||
|
"""
|
||||||
|
Creates a connection in uBridge.
|
||||||
|
|
||||||
|
:param nio: NIO instance
|
||||||
|
:param port_number: port number
|
||||||
|
"""
|
||||||
|
|
||||||
|
port_info = None
|
||||||
|
for port in self._ports:
|
||||||
|
if port["port_number"] == port_number:
|
||||||
|
port_info = port
|
||||||
|
break
|
||||||
|
|
||||||
|
if not port_info:
|
||||||
|
raise NodeError("Port {port_number} doesn't exist on cloud '{name}'".format(name=self.name,
|
||||||
|
port_number=port_number))
|
||||||
|
|
||||||
|
bridge_name = "{}-{}".format(self._id, port_number)
|
||||||
|
yield from self._ubridge_send("bridge create {name}".format(name=bridge_name))
|
||||||
|
if not isinstance(nio, NIOUDP):
|
||||||
|
raise NodeError("Source NIO is not UDP")
|
||||||
|
yield from self._ubridge_send('bridge add_nio_udp {name} {lport} {rhost} {rport}'.format(name=bridge_name,
|
||||||
|
lport=nio.lport,
|
||||||
|
rhost=nio.rhost,
|
||||||
|
rport=nio.rport))
|
||||||
|
|
||||||
|
if port_info["type"] in ("ethernet", "tap"):
|
||||||
|
network_interfaces = [interface["name"] for interface in interfaces()]
|
||||||
|
if not port_info["interface"] in network_interfaces:
|
||||||
|
raise NodeError("Interface {} could not be found on this system".format(port_info["interface"]))
|
||||||
|
|
||||||
|
if sys.platform.startswith("win"):
|
||||||
|
windows_interfaces = interfaces()
|
||||||
|
npf = None
|
||||||
|
for interface in windows_interfaces:
|
||||||
|
if port_info["interface"] == interface["name"]:
|
||||||
|
npf = interface["id"]
|
||||||
|
if npf:
|
||||||
|
yield from self._ubridge_send('bridge add_nio_ethernet {name} "{interface}"'.format(name=bridge_name,
|
||||||
|
interface=npf))
|
||||||
|
else:
|
||||||
|
raise NodeError("Could not find NPF id for interface {}".format(port_info["interface"]))
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
if port_info["type"] == "ethernet":
|
||||||
|
if sys.platform.startswith("linux"):
|
||||||
|
# use raw sockets on Linux
|
||||||
|
yield from self._ubridge_send('bridge add_nio_linux_raw {name} "{interface}"'.format(name=bridge_name,
|
||||||
|
interface=port_info["interface"]))
|
||||||
|
else:
|
||||||
|
yield from self._ubridge_send('bridge add_nio_ethernet {name} "{interface}"'.format(name=bridge_name,
|
||||||
|
interface=port_info["interface"]))
|
||||||
|
|
||||||
|
elif port_info["type"] == "tap":
|
||||||
|
yield from self._ubridge_send('bridge add_nio_tap {name} "{interface}"'.format(name=bridge_name,
|
||||||
|
interface=port_info["interface"]))
|
||||||
|
|
||||||
|
elif port_info["type"] == "udp":
|
||||||
|
yield from self._ubridge_send('bridge add_nio_udp {name} {lport} {rhost} {rport}'.format(name=bridge_name,
|
||||||
|
lport=port_info["lport"],
|
||||||
|
rhost=port_info["rhost"],
|
||||||
|
rport=port_info["rport"]))
|
||||||
|
|
||||||
|
if nio.capturing:
|
||||||
|
yield from self._ubridge_send('bridge start_capture {name} "{pcap_file}"'.format(name=bridge_name,
|
||||||
|
pcap_file=nio.pcap_output_file))
|
||||||
|
|
||||||
|
|
||||||
|
yield from self._ubridge_send('bridge start {name}'.format(name=bridge_name))
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def add_nio(self, nio, port_number):
|
def add_nio(self, nio, port_number):
|
||||||
@ -121,11 +201,18 @@ class Cloud(BaseNode):
|
|||||||
nio=nio,
|
nio=nio,
|
||||||
port=port_number))
|
port=port_number))
|
||||||
self._nios[port_number] = nio
|
self._nios[port_number] = nio
|
||||||
for port_settings in self._ports:
|
yield from self._add_ubridge_connection(nio, port_number)
|
||||||
if port_settings["port_number"] == port_number:
|
|
||||||
#yield from self.set_port_settings(port_number, port_settings)
|
|
||||||
break
|
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def _delete_ubridge_connection(self, port_number):
|
||||||
|
"""
|
||||||
|
Deletes a connection in uBridge.
|
||||||
|
|
||||||
|
:param port_number: adapter number
|
||||||
|
"""
|
||||||
|
|
||||||
|
bridge_name = "{}-{}".format(self._id, port_number)
|
||||||
|
yield from self._ubridge_send("bridge delete {name}".format(name=bridge_name))
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def remove_nio(self, port_number):
|
def remove_nio(self, port_number):
|
||||||
@ -150,6 +237,7 @@ class Cloud(BaseNode):
|
|||||||
port=port_number))
|
port=port_number))
|
||||||
|
|
||||||
del self._nios[port_number]
|
del self._nios[port_number]
|
||||||
|
yield from self._delete_ubridge_connection(port_number)
|
||||||
return nio
|
return nio
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
@ -162,7 +250,25 @@ class Cloud(BaseNode):
|
|||||||
:param data_link_type: PCAP data link type (DLT_*), default is DLT_EN10MB
|
:param data_link_type: PCAP data link type (DLT_*), default is DLT_EN10MB
|
||||||
"""
|
"""
|
||||||
|
|
||||||
raise NotImplementedError()
|
if not [port["port_number"] for port in self._ports if port_number == port["port_number"]]:
|
||||||
|
raise NodeError("Port {port_number} doesn't exist on cloud '{name}'".format(name=self.name,
|
||||||
|
port_number=port_number))
|
||||||
|
|
||||||
|
if port_number not in self._nios:
|
||||||
|
raise NodeError("Port {} is not connected".format(port_number))
|
||||||
|
|
||||||
|
nio = self._nios[port_number]
|
||||||
|
|
||||||
|
if nio.capturing:
|
||||||
|
raise NodeError("Packet capture is already activated on port {port_number}".format(port_number=port_number))
|
||||||
|
nio.startPacketCapture(output_file)
|
||||||
|
bridge_name = "{}-{}".format(self._id, port_number)
|
||||||
|
yield from self._ubridge_send('bridge start_capture {name} "{output_file}"'.format(name=bridge_name,
|
||||||
|
output_file=output_file))
|
||||||
|
log.info("Cloud '{name}' [{id}]: starting packet capture on port {port_number}".format(name=self.name,
|
||||||
|
id=self.id,
|
||||||
|
port_number=port_number))
|
||||||
|
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def stop_capture(self, port_number):
|
def stop_capture(self, port_number):
|
||||||
@ -172,4 +278,18 @@ class Cloud(BaseNode):
|
|||||||
:param port_number: allocated port number
|
:param port_number: allocated port number
|
||||||
"""
|
"""
|
||||||
|
|
||||||
raise NotImplementedError()
|
if not [port["port_number"] for port in self._ports if port_number == port["port_number"]]:
|
||||||
|
raise NodeError("Port {port_number} doesn't exist on cloud '{name}'".format(name=self.name,
|
||||||
|
port_number=port_number))
|
||||||
|
|
||||||
|
if port_number not in self._nios:
|
||||||
|
raise NodeError("Port {} is not connected".format(port_number))
|
||||||
|
|
||||||
|
nio = self._nios[port_number]
|
||||||
|
nio.stopPacketCapture()
|
||||||
|
bridge_name = "{}-{}".format(self._id, port_number)
|
||||||
|
yield from self._ubridge_send("bridge stop_capture {name}".format(name=bridge_name))
|
||||||
|
|
||||||
|
log.info("Cloud'{name}' [{id}]: stopping packet capture on port {port_number}".format(name=self.name,
|
||||||
|
id=self.id,
|
||||||
|
port_number=port_number))
|
||||||
|
@ -15,7 +15,10 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# 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 os
|
||||||
|
|
||||||
from gns3server.web.route import Route
|
from gns3server.web.route import Route
|
||||||
|
from gns3server.schemas.node import NODE_CAPTURE_SCHEMA
|
||||||
from gns3server.schemas.nio import NIO_SCHEMA
|
from gns3server.schemas.nio import NIO_SCHEMA
|
||||||
from gns3server.compute.builtin import Builtin
|
from gns3server.compute.builtin import Builtin
|
||||||
|
|
||||||
@ -132,7 +135,7 @@ class CloudHandler:
|
|||||||
description="Start a cloud")
|
description="Start a cloud")
|
||||||
def start(request, response):
|
def start(request, response):
|
||||||
|
|
||||||
Builtin.instance().get_device(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
Builtin.instance().get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||||
response.set_status(204)
|
response.set_status(204)
|
||||||
|
|
||||||
@Route.post(
|
@Route.post(
|
||||||
@ -149,7 +152,7 @@ class CloudHandler:
|
|||||||
description="Stop a cloud")
|
description="Stop a cloud")
|
||||||
def stop(request, response):
|
def stop(request, response):
|
||||||
|
|
||||||
Builtin.instance().get_device(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
Builtin.instance().get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||||
response.set_status(204)
|
response.set_status(204)
|
||||||
|
|
||||||
@Route.post(
|
@Route.post(
|
||||||
@ -166,7 +169,7 @@ class CloudHandler:
|
|||||||
description="Suspend a cloud")
|
description="Suspend a cloud")
|
||||||
def suspend(request, response):
|
def suspend(request, response):
|
||||||
|
|
||||||
Builtin.instance().get_device(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
Builtin.instance().get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||||
response.set_status(204)
|
response.set_status(204)
|
||||||
|
|
||||||
@Route.post(
|
@Route.post(
|
||||||
@ -189,7 +192,7 @@ class CloudHandler:
|
|||||||
|
|
||||||
builtin_manager = Builtin.instance()
|
builtin_manager = Builtin.instance()
|
||||||
node = builtin_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
node = builtin_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||||
nio = yield from builtin_manager.create_nio(node, request.json["nio"])
|
nio = builtin_manager.create_nio(node, request.json)
|
||||||
port_number = int(request.match_info["port_number"])
|
port_number = int(request.match_info["port_number"])
|
||||||
yield from node.add_nio(nio, port_number)
|
yield from node.add_nio(nio, port_number)
|
||||||
response.set_status(201)
|
response.set_status(201)
|
||||||
@ -214,6 +217,51 @@ class CloudHandler:
|
|||||||
builtin_manager = Builtin.instance()
|
builtin_manager = Builtin.instance()
|
||||||
node = builtin_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
node = builtin_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||||
port_number = int(request.match_info["port_number"])
|
port_number = int(request.match_info["port_number"])
|
||||||
nio = yield from node.remove_nio(port_number)
|
yield from node.remove_nio(port_number)
|
||||||
yield from nio.delete()
|
response.set_status(204)
|
||||||
|
|
||||||
|
@Route.post(
|
||||||
|
r"/projects/{project_id}/cloud/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/start_capture",
|
||||||
|
parameters={
|
||||||
|
"project_id": "Project UUID",
|
||||||
|
"node_id": "Node UUID",
|
||||||
|
"adapter_number": "Adapter on the cloud (always 0)",
|
||||||
|
"port_number": "Port on the cloud"
|
||||||
|
},
|
||||||
|
status_codes={
|
||||||
|
200: "Capture started",
|
||||||
|
400: "Invalid request",
|
||||||
|
404: "Instance doesn't exist"
|
||||||
|
},
|
||||||
|
description="Start a packet capture on a cloud instance",
|
||||||
|
input=NODE_CAPTURE_SCHEMA)
|
||||||
|
def start_capture(request, response):
|
||||||
|
|
||||||
|
builtin_manager = Builtin.instance()
|
||||||
|
node = builtin_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||||
|
port_number = int(request.match_info["port_number"])
|
||||||
|
pcap_file_path = os.path.join(node.project.capture_working_directory(), request.json["capture_file_name"])
|
||||||
|
yield from node.start_capture(port_number, pcap_file_path, request.json["data_link_type"])
|
||||||
|
response.json({"pcap_file_path": pcap_file_path})
|
||||||
|
|
||||||
|
@Route.post(
|
||||||
|
r"/projects/{project_id}/cloud/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/stop_capture",
|
||||||
|
parameters={
|
||||||
|
"project_id": "Project UUID",
|
||||||
|
"node_id": "Node UUID",
|
||||||
|
"adapter_number": "Adapter on the cloud (always 0)",
|
||||||
|
"port_number": "Port on the cloud"
|
||||||
|
},
|
||||||
|
status_codes={
|
||||||
|
204: "Capture stopped",
|
||||||
|
400: "Invalid request",
|
||||||
|
404: "Instance doesn't exist"
|
||||||
|
},
|
||||||
|
description="Stop a packet capture on a cloud instance")
|
||||||
|
def stop_capture(request, response):
|
||||||
|
|
||||||
|
builtin_manager = Builtin.instance()
|
||||||
|
node = builtin_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||||
|
port_number = int(request.match_info["port_number"])
|
||||||
|
yield from node.stop_capture(port_number)
|
||||||
response.set_status(204)
|
response.set_status(204)
|
||||||
|
Loading…
Reference in New Issue
Block a user