1
0
mirror of https://github.com/GNS3/gns3-server synced 2024-11-28 03:08:14 +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:
grossmj 2018-03-19 16:26:12 +07:00
parent cde30f8f53
commit 8b91894fa4
13 changed files with 157 additions and 53 deletions

View File

@ -418,8 +418,9 @@ class BaseManager:
sock.connect(sa) sock.connect(sa)
except OSError as e: except OSError as e:
raise aiohttp.web.HTTPInternalServerError(text="Could not create an UDP connection to {}:{}: {}".format(rhost, rport, 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)
nio = NIOUDP(lport, rhost, rport, filters) nio.filters = nio_settings.get("filters", {})
nio.suspend = nio_settings.get("suspend", False)
elif nio_settings["type"] == "nio_tap": elif nio_settings["type"] == "nio_tap":
tap_device = nio_settings["tap_device"] tap_device = nio_settings["tap_device"]
# if not is_interface_up(tap_device): # if not is_interface_up(tap_device):

View File

@ -39,6 +39,8 @@ class NIO:
self._hypervisor = hypervisor self._hypervisor = hypervisor
self._name = name self._name = name
self._filters = {}
self._suspended = False
self._bandwidth = None # no bandwidth constraint by default self._bandwidth = None # no bandwidth constraint by default
self._input_filter = None # no input filter applied by default self._input_filter = None # no input filter applied by default
self._output_filter = None # no output 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)) yield from self._hypervisor.send("nio set_bandwidth {name} {bandwidth}".format(name=self._name, bandwidth=bandwidth))
self._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): def __str__(self):
""" """
NIO string representation. NIO string representation.

View File

@ -41,27 +41,18 @@ class NIOUDP(NIO):
:param rport: remote port number :param rport: remote port number
""" """
def __init__(self, node, lport, rhost, rport, filters): def __init__(self, node, lport, rhost, rport):
# create an unique name # create an unique name
name = 'udp-{}'.format(uuid.uuid4()) name = 'udp-{}'.format(uuid.uuid4())
self._lport = lport self._lport = lport
self._rhost = rhost self._rhost = rhost
self._rport = rport self._rport = rport
self._filters = filters
self._local_tunnel_lport = None self._local_tunnel_lport = None
self._local_tunnel_rport = None self._local_tunnel_rport = None
self._node = node self._node = node
super().__init__(name, node.hypervisor) super().__init__(name, node.hypervisor)
@property
def filters(self):
return self._filters
@filters.setter
def filters(self, val):
self._filters = val
@asyncio.coroutine @asyncio.coroutine
def create(self): def create(self):
if not self._hypervisor: if not self._hypervisor:

View File

@ -36,8 +36,6 @@ log = logging.getLogger(__name__)
from ...base_node import BaseNode from ...base_node import BaseNode
from ..dynamips_error import DynamipsError from ..dynamips_error import DynamipsError
from ..nios.nio_udp import NIOUDP
from gns3server.utils.file_watcher import FileWatcher from gns3server.utils.file_watcher import FileWatcher
from gns3server.utils.asyncio import wait_run_in_executor, monitor_process, asyncio_ensure_future from gns3server.utils.asyncio import wait_run_in_executor, monitor_process, asyncio_ensure_future

View File

@ -29,6 +29,8 @@ class NIO(object):
def __init__(self): def __init__(self):
self._capturing = False self._capturing = False
self._suspended = False
self._filters = {}
self._pcap_output_file = "" self._pcap_output_file = ""
self._pcap_data_link_type = "" self._pcap_data_link_type = ""
@ -61,6 +63,7 @@ class NIO(object):
def pcap_output_file(self): def pcap_output_file(self):
""" """
Returns the path to the PCAP output file. Returns the path to the PCAP output file.
:returns: 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): def pcap_data_link_type(self):
""" """
Returns the PCAP data link type Returns the PCAP data link type
:returns: PCAP data link type (DLT_* value) :returns: PCAP data link type (DLT_* value)
""" """
return self._pcap_data_link_type 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

View File

@ -32,25 +32,12 @@ class NIOUDP(NIO):
:param rport: remote port number :param rport: remote port number
""" """
def __init__(self, lport, rhost, rport, filters): def __init__(self, lport, rhost, rport):
super().__init__() super().__init__()
self._lport = lport self._lport = lport
self._rhost = rhost self._rhost = rhost
self._rport = rport 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 @property
def lport(self): def lport(self):

View File

@ -957,6 +957,8 @@ class QemuVM(BaseNode):
yield from self.add_ubridge_udp_connection("QEMU-{}-{}".format(self._id, adapter_number), yield from self.add_ubridge_udp_connection("QEMU-{}-{}".format(self._id, adapter_number),
self._local_udp_tunnels[adapter_number][1], self._local_udp_tunnels[adapter_number][1],
nio) nio)
if nio.suspend:
yield from self._control_vm("set_link gns3-{} off".format(adapter_number))
else: else:
yield from self._control_vm("set_link gns3-{} off".format(adapter_number)) 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), yield from self.update_ubridge_udp_connection("QEMU-{}-{}".format(self._id, adapter_number),
self._local_udp_tunnels[adapter_number][1], self._local_udp_tunnels[adapter_number][1],
nio) 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: except IndexError:
raise QemuError('Adapter {adapter_number} does not exist on QEMU VM "{name}"'.format(name=self._name, raise QemuError('Adapter {adapter_number} does not exist on QEMU VM "{name}"'.format(name=self._name,
adapter_number=adapter_number)) adapter_number=adapter_number))
@ -1595,6 +1601,8 @@ class QemuVM(BaseNode):
nio.rport, nio.rport,
"127.0.0.1", "127.0.0.1",
nio.lport)]) nio.lport)])
elif isinstance(nio, NIOTAP):
network_options.extend(["-net", "tap,name=gns3-{},ifname={}".format(adapter_number, nio.tap_device)])
else: else:
network_options.extend(["-net", "nic,vlan={},macaddr={},model={}".format(adapter_number, mac, self._adapter_type)]) network_options.extend(["-net", "nic,vlan={},macaddr={},model={}".format(adapter_number, mac, self._adapter_type)])
@ -1614,6 +1622,8 @@ class QemuVM(BaseNode):
nio.rport, nio.rport,
"127.0.0.1", "127.0.0.1",
nio.lport)]) 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: else:
network_options.extend(["-device", device_string]) network_options.extend(["-device", device_string])

View File

@ -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{} 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{} 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("--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: if nio.capturing:
yield from self._modify_vm("--nictrace{} on".format(adapter_number + 1)) yield from self._modify_vm("--nictrace{} on".format(adapter_number + 1))
@ -1016,10 +1019,13 @@ class VirtualBoxVM(BaseNode):
if self.is_running(): if self.is_running():
try: try:
yield from self.update_ubridge_udp_connection( yield from self.update_ubridge_udp_connection("VBOX-{}-{}".format(self._id, adapter_number),
"VBOX-{}-{}".format(self._id, adapter_number), self._local_udp_tunnels[adapter_number][1],
self._local_udp_tunnels[adapter_number][1], nio)
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: except IndexError:
raise VirtualBoxError('Adapter {adapter_number} does not exist on VirtualBox VM "{name}"'.format( raise VirtualBoxError('Adapter {adapter_number} does not exist on VirtualBox VM "{name}"'.format(
name=self._name, name=self._name,

View File

@ -124,7 +124,7 @@ class Link:
self._streaming_pcap = None self._streaming_pcap = None
self._created = False self._created = False
self._link_type = "ethernet" self._link_type = "ethernet"
self._suspend = False self._suspended = False
self._filters = {} self._filters = {}
@property @property
@ -146,7 +146,8 @@ class Link:
Return the active filters. Return the active filters.
Filters are overridden if the link is suspended. 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 {"frequency_drop": [-1]}
return self._filters return self._filters
@ -178,8 +179,8 @@ class Link:
@asyncio.coroutine @asyncio.coroutine
def update_suspend(self, value): def update_suspend(self, value):
if value != self._suspend: if value != self._suspended:
self._suspend = value self._suspended = value
yield from self.update() yield from self.update()
self._project.controller.notification.emit("link.updated", self.__json__()) self._project.controller.notification.emit("link.updated", self.__json__())
self._project.dump() self._project.dump()
@ -452,7 +453,7 @@ class Link:
"nodes": res, "nodes": res,
"link_id": self._id, "link_id": self._id,
"filters": self._filters, "filters": self._filters,
"suspend": self._suspend "suspend": self._suspended
} }
return { return {
"nodes": res, "nodes": res,
@ -463,5 +464,5 @@ class Link:
"capture_file_path": self.capture_file_path, "capture_file_path": self.capture_file_path,
"link_type": self._link_type, "link_type": self._link_type,
"filters": self._filters, "filters": self._filters,
"suspend": self._suspend "suspend": self._suspended
} }

View File

@ -76,7 +76,8 @@ class UDPLink(Link):
"rhost": node2_host, "rhost": node2_host,
"rport": self._node2_port, "rport": self._node2_port,
"type": "nio_udp", "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) 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, "rhost": node1_host,
"rport": self._node1_port, "rport": self._node1_port,
"type": "nio_udp", "type": "nio_udp",
"filters": node2_filters "filters": node2_filters,
"suspend": self._suspended
}) })
try: 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) 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 @asyncio.coroutine
def update(self): def update(self):
"""
Update the link on the nodes
"""
if len(self._link_data) == 0: if len(self._link_data) == 0:
return return
node1 = self._nodes[0]["node"] node1 = self._nodes[0]["node"]
node2 = self._nodes[1]["node"] node2 = self._nodes[1]["node"]
filter_node = self._get_filter_node()
if node1 == filter_node: node1_filters = {}
adapter_number1 = self._nodes[0]["adapter_number"] node2_filters = {}
port_number1 = self._nodes[0]["port_number"] filter_node = self._get_filter_node()
self._link_data[0]["filters"] = self.get_active_filters() if filter_node == node1:
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) node1_filters = self.get_active_filters()
elif node2 == filter_node: elif filter_node == node2:
adapter_number2 = self._nodes[1]["adapter_number"] node2_filters = self.get_active_filters()
port_number2 = self._nodes[1]["port_number"]
self._link_data[1]["filters"] = self.get_active_filters() adapter_number1 = self._nodes[0]["adapter_number"]
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) 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 @asyncio.coroutine
def delete(self): def delete(self):

View File

@ -284,7 +284,7 @@ class QEMUHandler:
qemu_manager = Qemu.instance() qemu_manager = Qemu.instance()
vm = qemu_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"]) vm = qemu_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
nio_type = request.json["type"] 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)) raise aiohttp.web.HTTPConflict(text="NIO of type {} is not supported".format(nio_type))
nio = qemu_manager.create_nio(request.json) nio = qemu_manager.create_nio(request.json)
yield from vm.adapter_add_nio_binding(int(request.match_info["adapter_number"]), nio) 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"])] nio = vm.ethernet_adapters[int(request.match_info["adapter_number"])]
if "filters" in request.json and nio: if "filters" in request.json and nio:
nio.filters = request.json["filters"] 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) yield from vm.adapter_update_nio_binding(int(request.match_info["adapter_number"]), nio)
response.set_status(201) response.set_status(201)
response.json(request.json) response.json(request.json)

View File

@ -313,6 +313,8 @@ class VirtualBoxHandler:
nio = vm.ethernet_adapters[int(request.match_info["adapter_number"])] nio = vm.ethernet_adapters[int(request.match_info["adapter_number"])]
if "filters" in request.json and nio: if "filters" in request.json and nio:
nio.filters = request.json["filters"] 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) yield from vm.adapter_update_nio_binding(int(request.match_info["adapter_number"]), nio)
response.set_status(201) response.set_status(201)
response.json(request.json) response.json(request.json)

View File

@ -46,6 +46,10 @@ NIO_SCHEMA = {
"minimum": 1, "minimum": 1,
"maximum": 65535 "maximum": 65535
}, },
"suspend": {
"type": "boolean",
"description": "Suspend the link"
},
"filters": FILTER_OBJECT_SCHEMA "filters": FILTER_OBJECT_SCHEMA
}, },
"required": ["type", "lport", "rhost", "rport"], "required": ["type", "lport", "rhost", "rport"],