mirror of
https://github.com/GNS3/gns3-server
synced 2024-11-28 11:18:11 +00:00
Possibility to customize port names and adapter types for Qemu, VirtualBox, VMware and Docker. Fixes #2361.
MAC addresses can customized for Qemu as well.
This commit is contained in:
parent
509b171b06
commit
757c103c03
@ -77,6 +77,7 @@ class BaseNode:
|
||||
self._wrap_console = wrap_console
|
||||
self._wrapper_telnet_server = None
|
||||
self._internal_console_port = None
|
||||
self._custom_adapters = []
|
||||
|
||||
if self._console is not None:
|
||||
if console_type == "vnc":
|
||||
@ -123,6 +124,14 @@ class BaseNode:
|
||||
def linked_clone(self, val):
|
||||
self._linked_clone = val
|
||||
|
||||
@property
|
||||
def custom_adapters(self):
|
||||
return self._custom_adapters
|
||||
|
||||
@custom_adapters.setter
|
||||
def custom_adapters(self, val):
|
||||
self._custom_adapters = val
|
||||
|
||||
@property
|
||||
def status(self):
|
||||
"""
|
||||
@ -760,3 +769,10 @@ class BaseNode:
|
||||
percentage_left,
|
||||
platform.node())
|
||||
self.project.emit("log.warning", {"message": message})
|
||||
|
||||
def _get_custom_adapter_settings(self, adapter_number):
|
||||
|
||||
for custom_adapter in self.custom_adapters:
|
||||
if custom_adapter["adapter_number"] == adapter_number:
|
||||
return custom_adapter
|
||||
return {}
|
||||
|
@ -1696,10 +1696,17 @@ class QemuVM(BaseNode):
|
||||
if adapter_number not in self._local_udp_tunnels:
|
||||
self._local_udp_tunnels[adapter_number] = self._create_local_udp_tunnel()
|
||||
nio = self._local_udp_tunnels[adapter_number][0]
|
||||
|
||||
custom_adapter = self._get_custom_adapter_settings(adapter_number)
|
||||
adapter_type = custom_adapter.get("adapter_type", self._adapter_type)
|
||||
custom_mac_address = custom_adapter.get("mac_address")
|
||||
if custom_mac_address:
|
||||
mac = int_to_macaddress(macaddress_to_int(custom_mac_address))
|
||||
|
||||
if self._legacy_networking:
|
||||
# legacy QEMU networking syntax (-net)
|
||||
if nio:
|
||||
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, adapter_type)])
|
||||
if isinstance(nio, NIOUDP):
|
||||
if patched_qemu:
|
||||
# use patched Qemu syntax
|
||||
@ -1719,11 +1726,11 @@ class QemuVM(BaseNode):
|
||||
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)])
|
||||
network_options.extend(["-net", "nic,vlan={},macaddr={},model={}".format(adapter_number, mac, adapter_type)])
|
||||
|
||||
else:
|
||||
# newer QEMU networking syntax
|
||||
device_string = "{},mac={}".format(self._adapter_type, mac)
|
||||
device_string = "{},mac={}".format(adapter_type, mac)
|
||||
bridge_id = math.floor(pci_device_id / 32)
|
||||
if bridge_id > 0:
|
||||
addr = pci_device_id % 32
|
||||
|
@ -852,18 +852,22 @@ class VirtualBoxVM(BaseNode):
|
||||
continue
|
||||
|
||||
yield from self._modify_vm("--nictrace{} off".format(adapter_number + 1))
|
||||
|
||||
custom_adapter = self._get_custom_adapter_settings(adapter_number)
|
||||
adapter_type = custom_adapter.get("adapter_type", self._adapter_type)
|
||||
|
||||
vbox_adapter_type = "82540EM"
|
||||
if self._adapter_type == "PCnet-PCI II (Am79C970A)":
|
||||
if adapter_type == "PCnet-PCI II (Am79C970A)":
|
||||
vbox_adapter_type = "Am79C970A"
|
||||
if self._adapter_type == "PCNet-FAST III (Am79C973)":
|
||||
if adapter_type == "PCNet-FAST III (Am79C973)":
|
||||
vbox_adapter_type = "Am79C973"
|
||||
if self._adapter_type == "Intel PRO/1000 MT Desktop (82540EM)":
|
||||
if adapter_type == "Intel PRO/1000 MT Desktop (82540EM)":
|
||||
vbox_adapter_type = "82540EM"
|
||||
if self._adapter_type == "Intel PRO/1000 T Server (82543GC)":
|
||||
if adapter_type == "Intel PRO/1000 T Server (82543GC)":
|
||||
vbox_adapter_type = "82543GC"
|
||||
if self._adapter_type == "Intel PRO/1000 MT Server (82545EM)":
|
||||
if adapter_type == "Intel PRO/1000 MT Server (82545EM)":
|
||||
vbox_adapter_type = "82545EM"
|
||||
if self._adapter_type == "Paravirtualized Network (virtio-net)":
|
||||
if adapter_type == "Paravirtualized Network (virtio-net)":
|
||||
vbox_adapter_type = "virtio"
|
||||
args = [self._vmname, "--nictype{}".format(adapter_number + 1), vbox_adapter_type]
|
||||
yield from self.manager.execute("modifyvm", args)
|
||||
|
@ -258,17 +258,20 @@ class VMwareVM(BaseNode):
|
||||
self.manager.refresh_vmnet_list()
|
||||
for adapter_number in range(0, self._adapters):
|
||||
|
||||
custom_adapter = self._get_custom_adapter_settings(adapter_number)
|
||||
adapter_type = custom_adapter.get("adapter_type", self._adapter_type)
|
||||
|
||||
# add/update the interface
|
||||
if self._adapter_type == "default":
|
||||
if adapter_type == "default":
|
||||
# force default to e1000 because some guest OS don't detect the adapter (i.e. Windows 2012 server)
|
||||
# when 'virtualdev' is not set in the VMX file.
|
||||
adapter_type = "e1000"
|
||||
vmware_adapter_type = "e1000"
|
||||
else:
|
||||
adapter_type = self._adapter_type
|
||||
vmware_adapter_type = adapter_type
|
||||
ethernet_adapter = {"ethernet{}.present".format(adapter_number): "TRUE",
|
||||
"ethernet{}.addresstype".format(adapter_number): "generated",
|
||||
"ethernet{}.generatedaddressoffset".format(adapter_number): "0",
|
||||
"ethernet{}.virtualdev".format(adapter_number): adapter_type}
|
||||
"ethernet{}.virtualdev".format(adapter_number): vmware_adapter_type}
|
||||
self._vmx_pairs.update(ethernet_adapter)
|
||||
|
||||
connection_type = "ethernet{}.connectiontype".format(adapter_number)
|
||||
|
@ -74,6 +74,7 @@ class Node:
|
||||
self._z = 0
|
||||
self._ports = None
|
||||
self._symbol = None
|
||||
self._custom_adapters = []
|
||||
if node_type == "iou":
|
||||
self._port_name_format = "Ethernet{segment0}/{port0}"
|
||||
self._port_by_adapter = 4
|
||||
@ -305,6 +306,14 @@ class Node:
|
||||
def first_port_name(self, val):
|
||||
self._first_port_name = val
|
||||
|
||||
@property
|
||||
def custom_adapters(self):
|
||||
return self._custom_adapters
|
||||
|
||||
@custom_adapters.setter
|
||||
def custom_adapters(self, val):
|
||||
self._custom_adapters = val
|
||||
|
||||
def add_link(self, link):
|
||||
"""
|
||||
A link is connected to the node
|
||||
@ -330,6 +339,7 @@ class Node:
|
||||
data["node_id"] = self._id
|
||||
if self._node_type == "docker":
|
||||
timeout = None
|
||||
|
||||
else:
|
||||
timeout = 1200
|
||||
trial = 0
|
||||
@ -374,6 +384,9 @@ class Node:
|
||||
else:
|
||||
setattr(self, prop, kwargs[prop])
|
||||
|
||||
if compute_properties and "custom_adapters" in compute_properties:
|
||||
# we need to check custom adapters to update the custom port names
|
||||
self.custom_adapters = compute_properties["custom_adapters"]
|
||||
self._list_ports()
|
||||
if update_compute:
|
||||
data = self._node_data(properties=compute_properties)
|
||||
@ -442,6 +455,8 @@ class Node:
|
||||
data["console"] = self._console
|
||||
if self._console_type:
|
||||
data["console_type"] = self._console_type
|
||||
if self.custom_adapters:
|
||||
data["custom_adapters"] = self.custom_adapters
|
||||
|
||||
# None properties are not be send. Because it can mean the emulator doesn't support it
|
||||
for key in list(data.keys()):
|
||||
@ -585,7 +600,7 @@ class Node:
|
||||
"""
|
||||
Generate the list of port display in the client
|
||||
if the compute has sent a list we return it (use by
|
||||
node where you can not personnalize the port naming).
|
||||
node where you can not personalize the port naming).
|
||||
"""
|
||||
self._ports = []
|
||||
# Some special cases
|
||||
@ -615,7 +630,14 @@ class Node:
|
||||
return
|
||||
elif self._node_type == "docker":
|
||||
for adapter_number in range(0, self._properties["adapters"]):
|
||||
self._ports.append(PortFactory("eth{}".format(adapter_number), 0, adapter_number, 0, "ethernet", short_name="eth{}".format(adapter_number)))
|
||||
custom_adapter_settings = {}
|
||||
for custom_adapter in self.custom_adapters:
|
||||
if custom_adapter["adapter_number"] == adapter_number:
|
||||
custom_adapter_settings = custom_adapter
|
||||
break
|
||||
port_name = "eth{}".format(adapter_number)
|
||||
port_name = custom_adapter_settings.get("port_name", port_name)
|
||||
self._ports.append(PortFactory(port_name, 0, adapter_number, 0, "ethernet", short_name="eth{}".format(adapter_number)))
|
||||
elif self._node_type in ("ethernet_switch", "ethernet_hub"):
|
||||
# Basic node we don't want to have adapter number
|
||||
port_number = 0
|
||||
@ -630,7 +652,7 @@ class Node:
|
||||
self._ports.append(PortFactory(port["name"], 0, 0, port_number, "ethernet", short_name=port["name"]))
|
||||
port_number += 1
|
||||
else:
|
||||
self._ports = StandardPortFactory(self._properties, self._port_by_adapter, self._first_port_name, self._port_name_format, self._port_segment_size)
|
||||
self._ports = StandardPortFactory(self._properties, self._port_by_adapter, self._first_port_name, self._port_name_format, self._port_segment_size, self._custom_adapters)
|
||||
|
||||
def __repr__(self):
|
||||
return "<gns3server.controller.Node {} {}>".format(self._node_type, self._name)
|
||||
@ -644,6 +666,7 @@ class Node:
|
||||
"""
|
||||
:param topology_dump: Filter to keep only properties require for saving on disk
|
||||
"""
|
||||
|
||||
if topology_dump:
|
||||
return {
|
||||
"compute_id": str(self._compute.id),
|
||||
@ -662,7 +685,8 @@ class Node:
|
||||
"symbol": self._symbol,
|
||||
"port_name_format": self._port_name_format,
|
||||
"port_segment_size": self._port_segment_size,
|
||||
"first_port_name": self._first_port_name
|
||||
"first_port_name": self._first_port_name,
|
||||
"custom_adapters": self._custom_adapters
|
||||
}
|
||||
return {
|
||||
"compute_id": str(self._compute.id),
|
||||
@ -687,5 +711,6 @@ class Node:
|
||||
"port_name_format": self._port_name_format,
|
||||
"port_segment_size": self._port_segment_size,
|
||||
"first_port_name": self._first_port_name,
|
||||
"custom_adapters": self._custom_adapters,
|
||||
"ports": [port.__json__() for port in self.ports]
|
||||
}
|
||||
|
@ -51,7 +51,7 @@ class StandardPortFactory:
|
||||
"""
|
||||
Create ports for standard device
|
||||
"""
|
||||
def __new__(cls, properties, port_by_adapter, first_port_name, port_name_format, port_segment_size):
|
||||
def __new__(cls, properties, port_by_adapter, first_port_name, port_name_format, port_segment_size, custom_adapters):
|
||||
ports = []
|
||||
adapter_number = interface_number = segment_number = 0
|
||||
|
||||
@ -61,9 +61,16 @@ class StandardPortFactory:
|
||||
ethernet_adapters = properties.get("adapters", 1)
|
||||
|
||||
for adapter_number in range(adapter_number, ethernet_adapters + adapter_number):
|
||||
|
||||
custom_adapter_settings = {}
|
||||
for custom_adapter in custom_adapters:
|
||||
if custom_adapter["adapter_number"] == adapter_number:
|
||||
custom_adapter_settings = custom_adapter
|
||||
break
|
||||
|
||||
for port_number in range(0, port_by_adapter):
|
||||
if first_port_name and adapter_number == 0:
|
||||
port_name = first_port_name
|
||||
port_name = custom_adapter_settings.get("port_name", first_port_name)
|
||||
port = PortFactory(port_name, segment_number, adapter_number, port_number, "ethernet")
|
||||
else:
|
||||
try:
|
||||
@ -74,6 +81,8 @@ class StandardPortFactory:
|
||||
**cls._generate_replacement(interface_number, segment_number))
|
||||
except (ValueError, KeyError) as e:
|
||||
raise aiohttp.web.HTTPConflict(text="Invalid port name format {}: {}".format(port_name_format, str(e)))
|
||||
|
||||
port_name = custom_adapter_settings.get("port_name", port_name)
|
||||
port = PortFactory(port_name, segment_number, adapter_number, port_number, "ethernet")
|
||||
interface_number += 1
|
||||
if port_segment_size:
|
||||
|
@ -221,7 +221,7 @@ class DockerHandler:
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Adapter where the nio should be added",
|
||||
"port_number": "Port on the adapter"
|
||||
"port_number": "Port on the adapter (always 0)"
|
||||
},
|
||||
status_codes={
|
||||
201: "NIO created",
|
||||
|
49
gns3server/schemas/custom_adapters.py
Normal file
49
gns3server/schemas/custom_adapters.py
Normal file
@ -0,0 +1,49 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright (C) 2016 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
CUSTOM_ADAPTERS_ARRAY_SCHEMA = {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"description": "Custom properties",
|
||||
"properties": {
|
||||
"adapter_number": {
|
||||
"type": "integer",
|
||||
"description": "Adapter number"
|
||||
},
|
||||
"port_name": {
|
||||
"type": "string",
|
||||
"description": "Custom port name",
|
||||
"minLength": 1,
|
||||
},
|
||||
"adapter_type": {
|
||||
"type": "string",
|
||||
"description": "Custom adapter type",
|
||||
"minLength": 1,
|
||||
},
|
||||
"mac_address": {
|
||||
"description": "Custom MAC address",
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"pattern": "^([0-9a-fA-F]{2}[:]){5}([0-9a-fA-F]{2})$"
|
||||
},
|
||||
},
|
||||
"additionalProperties": False,
|
||||
"required": ["adapter_number"]
|
||||
},
|
||||
}
|
||||
|
@ -16,6 +16,9 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
from .custom_adapters import CUSTOM_ADAPTERS_ARRAY_SCHEMA
|
||||
|
||||
|
||||
DOCKER_CREATE_SCHEMA = {
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"description": "Request validation to create a new Docker container",
|
||||
@ -93,7 +96,8 @@ DOCKER_CREATE_SCHEMA = {
|
||||
"minLength": 12,
|
||||
"maxLength": 64,
|
||||
"pattern": "^[a-f0-9]+$"
|
||||
}
|
||||
},
|
||||
"custom_adapters": CUSTOM_ADAPTERS_ARRAY_SCHEMA # not used at this time
|
||||
},
|
||||
"additionalProperties": False,
|
||||
"required": ["name", "image"]
|
||||
@ -192,6 +196,7 @@ DOCKER_OBJECT_SCHEMA = {
|
||||
"description": "VM status Read only",
|
||||
"enum": ["started", "stopped", "suspended"]
|
||||
},
|
||||
"custom_adapters": CUSTOM_ADAPTERS_ARRAY_SCHEMA # not used at this time
|
||||
},
|
||||
"additionalProperties": False,
|
||||
}
|
||||
|
@ -17,6 +17,7 @@
|
||||
|
||||
import copy
|
||||
from .label import LABEL_OBJECT_SCHEMA
|
||||
from .custom_adapters import CUSTOM_ADAPTERS_ARRAY_SCHEMA
|
||||
|
||||
NODE_TYPE_SCHEMA = {
|
||||
"description": "Type of node",
|
||||
@ -194,6 +195,7 @@ NODE_OBJECT_SCHEMA = {
|
||||
"description": "Name of the first port",
|
||||
"type": ["string", "null"],
|
||||
},
|
||||
"custom_adapters": CUSTOM_ADAPTERS_ARRAY_SCHEMA,
|
||||
"ports": {
|
||||
"description": "List of node ports READ only",
|
||||
"type": "array",
|
||||
|
@ -15,6 +15,8 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from .custom_adapters import CUSTOM_ADAPTERS_ARRAY_SCHEMA
|
||||
|
||||
QEMU_PLATFORMS = ["aarch64", "alpha", "arm", "cris", "i386", "lm32", "m68k", "microblaze", "microblazeel", "mips", "mips64", "mips64el", "mipsel", "moxie", "or32", "ppc", "ppc64", "ppcemb", "s390x", "sh4", "sh4eb", "sparc", "sparc64", "tricore", "unicore32", "x86_64", "xtensa", "xtensaeb"]
|
||||
|
||||
|
||||
@ -207,7 +209,8 @@ QEMU_CREATE_SCHEMA = {
|
||||
"options": {
|
||||
"description": "Additional QEMU options",
|
||||
"type": ["string", "null"],
|
||||
}
|
||||
},
|
||||
"custom_adapters": CUSTOM_ADAPTERS_ARRAY_SCHEMA
|
||||
},
|
||||
"additionalProperties": False,
|
||||
"required": ["name"],
|
||||
@ -392,7 +395,8 @@ QEMU_UPDATE_SCHEMA = {
|
||||
"options": {
|
||||
"description": "Additional QEMU options",
|
||||
"type": ["string", "null"],
|
||||
}
|
||||
},
|
||||
"custom_adapters": CUSTOM_ADAPTERS_ARRAY_SCHEMA
|
||||
},
|
||||
"additionalProperties": False,
|
||||
}
|
||||
|
@ -16,6 +16,9 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
from .custom_adapters import CUSTOM_ADAPTERS_ARRAY_SCHEMA
|
||||
|
||||
|
||||
VBOX_CREATE_SCHEMA = {
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"description": "Request validation to create a new VirtualBox VM instance",
|
||||
@ -84,6 +87,7 @@ VBOX_CREATE_SCHEMA = {
|
||||
"description": "Action to execute on the VM is closed",
|
||||
"enum": ["power_off", "shutdown_signal", "save_vm_state"],
|
||||
},
|
||||
"custom_adapters": CUSTOM_ADAPTERS_ARRAY_SCHEMA
|
||||
},
|
||||
"additionalProperties": False,
|
||||
"required": ["name", "vmname"],
|
||||
@ -169,7 +173,8 @@ VBOX_OBJECT_SCHEMA = {
|
||||
"linked_clone": {
|
||||
"description": "Whether the VM is a linked clone or not",
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"custom_adapters": CUSTOM_ADAPTERS_ARRAY_SCHEMA
|
||||
},
|
||||
"additionalProperties": False,
|
||||
}
|
||||
|
@ -15,6 +15,8 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from .custom_adapters import CUSTOM_ADAPTERS_ARRAY_SCHEMA
|
||||
|
||||
|
||||
VMWARE_CREATE_SCHEMA = {
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
@ -74,7 +76,8 @@ VMWARE_CREATE_SCHEMA = {
|
||||
"use_any_adapter": {
|
||||
"description": "Allow GNS3 to use any VMware adapter",
|
||||
"type": "boolean",
|
||||
}
|
||||
},
|
||||
"custom_adapters": CUSTOM_ADAPTERS_ARRAY_SCHEMA
|
||||
},
|
||||
"additionalProperties": False,
|
||||
"required": ["name", "vmx_path", "linked_clone"],
|
||||
@ -154,7 +157,8 @@ VMWARE_OBJECT_SCHEMA = {
|
||||
"linked_clone": {
|
||||
"description": "Whether the VM is a linked clone or not",
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"custom_adapters": CUSTOM_ADAPTERS_ARRAY_SCHEMA
|
||||
},
|
||||
"additionalProperties": False
|
||||
}
|
||||
|
@ -141,6 +141,7 @@ def test_json(node, compute):
|
||||
"port_name_format": "Ethernet{0}",
|
||||
"port_segment_size": 0,
|
||||
"first_port_name": None,
|
||||
"custom_adapters": [],
|
||||
"ports": [
|
||||
{
|
||||
"adapter_number": 0,
|
||||
@ -169,7 +170,8 @@ def test_json(node, compute):
|
||||
"label": node.label,
|
||||
"port_name_format": "Ethernet{0}",
|
||||
"port_segment_size": 0,
|
||||
"first_port_name": None
|
||||
"first_port_name": None,
|
||||
"custom_adapters": []
|
||||
}
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user