mirror of
https://github.com/GNS3/gns3-server.git
synced 2024-12-20 05:17:56 +00:00
Improve suspend a link for Qemu and VirtualBox VMs.
A suspended link will be unplugged allowing the VMs to be notified of the change.
This commit is contained in:
parent
cde30f8f53
commit
8b91894fa4
@ -418,8 +418,9 @@ class BaseManager:
|
||||
sock.connect(sa)
|
||||
except OSError as e:
|
||||
raise aiohttp.web.HTTPInternalServerError(text="Could not create an UDP connection to {}:{}: {}".format(rhost, rport, e))
|
||||
filters = nio_settings.get("filters", {})
|
||||
nio = NIOUDP(lport, rhost, rport, filters)
|
||||
nio = NIOUDP(lport, rhost, rport)
|
||||
nio.filters = nio_settings.get("filters", {})
|
||||
nio.suspend = nio_settings.get("suspend", False)
|
||||
elif nio_settings["type"] == "nio_tap":
|
||||
tap_device = nio_settings["tap_device"]
|
||||
# if not is_interface_up(tap_device):
|
||||
|
@ -39,6 +39,8 @@ class NIO:
|
||||
|
||||
self._hypervisor = hypervisor
|
||||
self._name = name
|
||||
self._filters = {}
|
||||
self._suspended = False
|
||||
self._bandwidth = None # no bandwidth constraint by default
|
||||
self._input_filter = None # no input filter applied by default
|
||||
self._output_filter = None # no output filter applied by default
|
||||
@ -236,6 +238,47 @@ class NIO:
|
||||
yield from self._hypervisor.send("nio set_bandwidth {name} {bandwidth}".format(name=self._name, bandwidth=bandwidth))
|
||||
self._bandwidth = bandwidth
|
||||
|
||||
@property
|
||||
def suspend(self):
|
||||
"""
|
||||
Returns if this link is suspended or not.
|
||||
|
||||
:returns: boolean
|
||||
"""
|
||||
|
||||
return self._suspended
|
||||
|
||||
@suspend.setter
|
||||
def suspend(self, suspended):
|
||||
"""
|
||||
Suspend this link.
|
||||
|
||||
:param suspended: boolean
|
||||
"""
|
||||
|
||||
self._suspended = suspended
|
||||
|
||||
@property
|
||||
def filters(self):
|
||||
"""
|
||||
Returns the list of packet filters for this NIO.
|
||||
|
||||
:returns: packet filters (dictionary)
|
||||
"""
|
||||
|
||||
return self._filters
|
||||
|
||||
@filters.setter
|
||||
def filters(self, new_filters):
|
||||
"""
|
||||
Set a list of packet filters for this NIO.
|
||||
|
||||
:param new_filters: packet filters (dictionary)
|
||||
"""
|
||||
|
||||
assert isinstance(new_filters, dict)
|
||||
self._filters = new_filters
|
||||
|
||||
def __str__(self):
|
||||
"""
|
||||
NIO string representation.
|
||||
|
@ -41,27 +41,18 @@ class NIOUDP(NIO):
|
||||
:param rport: remote port number
|
||||
"""
|
||||
|
||||
def __init__(self, node, lport, rhost, rport, filters):
|
||||
def __init__(self, node, lport, rhost, rport):
|
||||
|
||||
# create an unique name
|
||||
name = 'udp-{}'.format(uuid.uuid4())
|
||||
self._lport = lport
|
||||
self._rhost = rhost
|
||||
self._rport = rport
|
||||
self._filters = filters
|
||||
self._local_tunnel_lport = None
|
||||
self._local_tunnel_rport = None
|
||||
self._node = node
|
||||
super().__init__(name, node.hypervisor)
|
||||
|
||||
@property
|
||||
def filters(self):
|
||||
return self._filters
|
||||
|
||||
@filters.setter
|
||||
def filters(self, val):
|
||||
self._filters = val
|
||||
|
||||
@asyncio.coroutine
|
||||
def create(self):
|
||||
if not self._hypervisor:
|
||||
|
@ -36,8 +36,6 @@ log = logging.getLogger(__name__)
|
||||
|
||||
from ...base_node import BaseNode
|
||||
from ..dynamips_error import DynamipsError
|
||||
from ..nios.nio_udp import NIOUDP
|
||||
|
||||
|
||||
from gns3server.utils.file_watcher import FileWatcher
|
||||
from gns3server.utils.asyncio import wait_run_in_executor, monitor_process, asyncio_ensure_future
|
||||
|
@ -29,6 +29,8 @@ class NIO(object):
|
||||
def __init__(self):
|
||||
|
||||
self._capturing = False
|
||||
self._suspended = False
|
||||
self._filters = {}
|
||||
self._pcap_output_file = ""
|
||||
self._pcap_data_link_type = ""
|
||||
|
||||
@ -61,6 +63,7 @@ class NIO(object):
|
||||
def pcap_output_file(self):
|
||||
"""
|
||||
Returns the path to the PCAP output file.
|
||||
|
||||
:returns: path to the PCAP output file
|
||||
"""
|
||||
|
||||
@ -70,7 +73,49 @@ class NIO(object):
|
||||
def pcap_data_link_type(self):
|
||||
"""
|
||||
Returns the PCAP data link type
|
||||
|
||||
:returns: PCAP data link type (DLT_* value)
|
||||
"""
|
||||
|
||||
return self._pcap_data_link_type
|
||||
|
||||
@property
|
||||
def suspend(self):
|
||||
"""
|
||||
Returns if this link is suspended or not.
|
||||
|
||||
:returns: boolean
|
||||
"""
|
||||
|
||||
return self._suspended
|
||||
|
||||
@suspend.setter
|
||||
def suspend(self, suspended):
|
||||
"""
|
||||
Suspend this link.
|
||||
|
||||
:param suspended: boolean
|
||||
"""
|
||||
|
||||
self._suspended = suspended
|
||||
|
||||
@property
|
||||
def filters(self):
|
||||
"""
|
||||
Returns the list of packet filters for this NIO.
|
||||
|
||||
:returns: packet filters (dictionary)
|
||||
"""
|
||||
|
||||
return self._filters
|
||||
|
||||
@filters.setter
|
||||
def filters(self, new_filters):
|
||||
"""
|
||||
Set a list of packet filters for this NIO.
|
||||
|
||||
:param new_filters: packet filters (dictionary)
|
||||
"""
|
||||
|
||||
assert isinstance(new_filters, dict)
|
||||
self._filters = new_filters
|
||||
|
@ -32,25 +32,12 @@ class NIOUDP(NIO):
|
||||
:param rport: remote port number
|
||||
"""
|
||||
|
||||
def __init__(self, lport, rhost, rport, filters):
|
||||
def __init__(self, lport, rhost, rport):
|
||||
|
||||
super().__init__()
|
||||
self._lport = lport
|
||||
self._rhost = rhost
|
||||
self._rport = rport
|
||||
assert isinstance(filters, dict)
|
||||
self._filters = filters
|
||||
|
||||
@property
|
||||
def filters(self):
|
||||
"""
|
||||
Return the list of filter on this NIO
|
||||
"""
|
||||
return self._filters
|
||||
|
||||
@filters.setter
|
||||
def filters(self, val):
|
||||
self._filters = val
|
||||
|
||||
@property
|
||||
def lport(self):
|
||||
|
@ -957,6 +957,8 @@ class QemuVM(BaseNode):
|
||||
yield from self.add_ubridge_udp_connection("QEMU-{}-{}".format(self._id, adapter_number),
|
||||
self._local_udp_tunnels[adapter_number][1],
|
||||
nio)
|
||||
if nio.suspend:
|
||||
yield from self._control_vm("set_link gns3-{} off".format(adapter_number))
|
||||
else:
|
||||
yield from self._control_vm("set_link gns3-{} off".format(adapter_number))
|
||||
|
||||
@ -1191,6 +1193,10 @@ class QemuVM(BaseNode):
|
||||
yield from self.update_ubridge_udp_connection("QEMU-{}-{}".format(self._id, adapter_number),
|
||||
self._local_udp_tunnels[adapter_number][1],
|
||||
nio)
|
||||
if nio.suspend:
|
||||
yield from self._control_vm("set_link gns3-{} off".format(adapter_number))
|
||||
else:
|
||||
yield from self._control_vm("set_link gns3-{} on".format(adapter_number))
|
||||
except IndexError:
|
||||
raise QemuError('Adapter {adapter_number} does not exist on QEMU VM "{name}"'.format(name=self._name,
|
||||
adapter_number=adapter_number))
|
||||
@ -1595,6 +1601,8 @@ class QemuVM(BaseNode):
|
||||
nio.rport,
|
||||
"127.0.0.1",
|
||||
nio.lport)])
|
||||
elif isinstance(nio, NIOTAP):
|
||||
network_options.extend(["-net", "tap,name=gns3-{},ifname={}".format(adapter_number, nio.tap_device)])
|
||||
else:
|
||||
network_options.extend(["-net", "nic,vlan={},macaddr={},model={}".format(adapter_number, mac, self._adapter_type)])
|
||||
|
||||
@ -1614,6 +1622,8 @@ class QemuVM(BaseNode):
|
||||
nio.rport,
|
||||
"127.0.0.1",
|
||||
nio.lport)])
|
||||
elif isinstance(nio, NIOTAP):
|
||||
network_options.extend(["-netdev", "tap,id=gns3-{},ifname={},script=no,downscript=no".format(adapter_number, nio.tap_device)])
|
||||
else:
|
||||
network_options.extend(["-device", device_string])
|
||||
|
||||
|
@ -873,7 +873,10 @@ class VirtualBoxVM(BaseNode):
|
||||
yield from self._modify_vm("--nicproperty{} sport={}".format(adapter_number + 1, nio.lport))
|
||||
yield from self._modify_vm("--nicproperty{} dest={}".format(adapter_number + 1, nio.rhost))
|
||||
yield from self._modify_vm("--nicproperty{} dport={}".format(adapter_number + 1, nio.rport))
|
||||
yield from self._modify_vm("--cableconnected{} on".format(adapter_number + 1))
|
||||
if nio.suspend:
|
||||
yield from self._modify_vm("--cableconnected{} off".format(adapter_number + 1))
|
||||
else:
|
||||
yield from self._modify_vm("--cableconnected{} on".format(adapter_number + 1))
|
||||
|
||||
if nio.capturing:
|
||||
yield from self._modify_vm("--nictrace{} on".format(adapter_number + 1))
|
||||
@ -1016,10 +1019,13 @@ class VirtualBoxVM(BaseNode):
|
||||
|
||||
if self.is_running():
|
||||
try:
|
||||
yield from self.update_ubridge_udp_connection(
|
||||
"VBOX-{}-{}".format(self._id, adapter_number),
|
||||
self._local_udp_tunnels[adapter_number][1],
|
||||
nio)
|
||||
yield from self.update_ubridge_udp_connection("VBOX-{}-{}".format(self._id, adapter_number),
|
||||
self._local_udp_tunnels[adapter_number][1],
|
||||
nio)
|
||||
if nio.suspend:
|
||||
yield from self._control_vm("setlinkstate{} off".format(adapter_number + 1))
|
||||
else:
|
||||
yield from self._control_vm("setlinkstate{} on".format(adapter_number + 1))
|
||||
except IndexError:
|
||||
raise VirtualBoxError('Adapter {adapter_number} does not exist on VirtualBox VM "{name}"'.format(
|
||||
name=self._name,
|
||||
|
@ -124,7 +124,7 @@ class Link:
|
||||
self._streaming_pcap = None
|
||||
self._created = False
|
||||
self._link_type = "ethernet"
|
||||
self._suspend = False
|
||||
self._suspended = False
|
||||
self._filters = {}
|
||||
|
||||
@property
|
||||
@ -146,7 +146,8 @@ class Link:
|
||||
Return the active filters.
|
||||
Filters are overridden if the link is suspended.
|
||||
"""
|
||||
if self._suspend:
|
||||
if self._suspended:
|
||||
# this is to allow all node types to support suspend link
|
||||
return {"frequency_drop": [-1]}
|
||||
return self._filters
|
||||
|
||||
@ -178,8 +179,8 @@ class Link:
|
||||
|
||||
@asyncio.coroutine
|
||||
def update_suspend(self, value):
|
||||
if value != self._suspend:
|
||||
self._suspend = value
|
||||
if value != self._suspended:
|
||||
self._suspended = value
|
||||
yield from self.update()
|
||||
self._project.controller.notification.emit("link.updated", self.__json__())
|
||||
self._project.dump()
|
||||
@ -452,7 +453,7 @@ class Link:
|
||||
"nodes": res,
|
||||
"link_id": self._id,
|
||||
"filters": self._filters,
|
||||
"suspend": self._suspend
|
||||
"suspend": self._suspended
|
||||
}
|
||||
return {
|
||||
"nodes": res,
|
||||
@ -463,5 +464,5 @@ class Link:
|
||||
"capture_file_path": self.capture_file_path,
|
||||
"link_type": self._link_type,
|
||||
"filters": self._filters,
|
||||
"suspend": self._suspend
|
||||
"suspend": self._suspended
|
||||
}
|
||||
|
@ -76,7 +76,8 @@ class UDPLink(Link):
|
||||
"rhost": node2_host,
|
||||
"rport": self._node2_port,
|
||||
"type": "nio_udp",
|
||||
"filters": node1_filters
|
||||
"filters": node1_filters,
|
||||
"suspend": self._suspended
|
||||
})
|
||||
yield from node1.post("/adapters/{adapter_number}/ports/{port_number}/nio".format(adapter_number=adapter_number1, port_number=port_number1), data=self._link_data[0], timeout=120)
|
||||
|
||||
@ -85,7 +86,8 @@ class UDPLink(Link):
|
||||
"rhost": node1_host,
|
||||
"rport": self._node1_port,
|
||||
"type": "nio_udp",
|
||||
"filters": node2_filters
|
||||
"filters": node2_filters,
|
||||
"suspend": self._suspended
|
||||
})
|
||||
try:
|
||||
yield from node2.post("/adapters/{adapter_number}/ports/{port_number}/nio".format(adapter_number=adapter_number2, port_number=port_number2), data=self._link_data[1], timeout=120)
|
||||
@ -97,22 +99,34 @@ class UDPLink(Link):
|
||||
|
||||
@asyncio.coroutine
|
||||
def update(self):
|
||||
"""
|
||||
Update the link on the nodes
|
||||
"""
|
||||
|
||||
if len(self._link_data) == 0:
|
||||
return
|
||||
node1 = self._nodes[0]["node"]
|
||||
node2 = self._nodes[1]["node"]
|
||||
filter_node = self._get_filter_node()
|
||||
|
||||
if node1 == filter_node:
|
||||
adapter_number1 = self._nodes[0]["adapter_number"]
|
||||
port_number1 = self._nodes[0]["port_number"]
|
||||
self._link_data[0]["filters"] = self.get_active_filters()
|
||||
yield from node1.put("/adapters/{adapter_number}/ports/{port_number}/nio".format(adapter_number=adapter_number1, port_number=port_number1), data=self._link_data[0], timeout=120)
|
||||
elif node2 == filter_node:
|
||||
adapter_number2 = self._nodes[1]["adapter_number"]
|
||||
port_number2 = self._nodes[1]["port_number"]
|
||||
self._link_data[1]["filters"] = self.get_active_filters()
|
||||
yield from node2.put("/adapters/{adapter_number}/ports/{port_number}/nio".format(adapter_number=adapter_number2, port_number=port_number2), data=self._link_data[1], timeout=221)
|
||||
node1_filters = {}
|
||||
node2_filters = {}
|
||||
filter_node = self._get_filter_node()
|
||||
if filter_node == node1:
|
||||
node1_filters = self.get_active_filters()
|
||||
elif filter_node == node2:
|
||||
node2_filters = self.get_active_filters()
|
||||
|
||||
adapter_number1 = self._nodes[0]["adapter_number"]
|
||||
port_number1 = self._nodes[0]["port_number"]
|
||||
self._link_data[0]["filters"] = node1_filters
|
||||
self._link_data[0]["suspend"] = self._suspended
|
||||
yield from node1.put("/adapters/{adapter_number}/ports/{port_number}/nio".format(adapter_number=adapter_number1, port_number=port_number1), data=self._link_data[0], timeout=120)
|
||||
|
||||
adapter_number2 = self._nodes[1]["adapter_number"]
|
||||
port_number2 = self._nodes[1]["port_number"]
|
||||
self._link_data[1]["filters"] = node2_filters
|
||||
self._link_data[1]["suspend"] = self._suspended
|
||||
yield from node2.put("/adapters/{adapter_number}/ports/{port_number}/nio".format(adapter_number=adapter_number2, port_number=port_number2), data=self._link_data[1], timeout=221)
|
||||
|
||||
@asyncio.coroutine
|
||||
def delete(self):
|
||||
|
@ -284,7 +284,7 @@ class QEMUHandler:
|
||||
qemu_manager = Qemu.instance()
|
||||
vm = qemu_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
nio_type = request.json["type"]
|
||||
if nio_type not in ("nio_udp", "nio_tap", "nio_nat"):
|
||||
if nio_type not in ("nio_udp"):
|
||||
raise aiohttp.web.HTTPConflict(text="NIO of type {} is not supported".format(nio_type))
|
||||
nio = qemu_manager.create_nio(request.json)
|
||||
yield from vm.adapter_add_nio_binding(int(request.match_info["adapter_number"]), nio)
|
||||
@ -314,6 +314,8 @@ class QEMUHandler:
|
||||
nio = vm.ethernet_adapters[int(request.match_info["adapter_number"])]
|
||||
if "filters" in request.json and nio:
|
||||
nio.filters = request.json["filters"]
|
||||
if "suspend" in request.json and nio:
|
||||
nio.suspend = request.json["suspend"]
|
||||
yield from vm.adapter_update_nio_binding(int(request.match_info["adapter_number"]), nio)
|
||||
response.set_status(201)
|
||||
response.json(request.json)
|
||||
|
@ -313,6 +313,8 @@ class VirtualBoxHandler:
|
||||
nio = vm.ethernet_adapters[int(request.match_info["adapter_number"])]
|
||||
if "filters" in request.json and nio:
|
||||
nio.filters = request.json["filters"]
|
||||
if "suspend" in request.json and nio:
|
||||
nio.suspend = request.json["suspend"]
|
||||
yield from vm.adapter_update_nio_binding(int(request.match_info["adapter_number"]), nio)
|
||||
response.set_status(201)
|
||||
response.json(request.json)
|
||||
|
@ -46,6 +46,10 @@ NIO_SCHEMA = {
|
||||
"minimum": 1,
|
||||
"maximum": 65535
|
||||
},
|
||||
"suspend": {
|
||||
"type": "boolean",
|
||||
"description": "Suspend the link"
|
||||
},
|
||||
"filters": FILTER_OBJECT_SCHEMA
|
||||
},
|
||||
"required": ["type", "lport", "rhost", "rport"],
|
||||
|
Loading…
Reference in New Issue
Block a user