diff --git a/gns3server/handlers/api/vmware_handler.py b/gns3server/handlers/api/vmware_handler.py index fda0bd19..9370d795 100644 --- a/gns3server/handlers/api/vmware_handler.py +++ b/gns3server/handlers/api/vmware_handler.py @@ -268,7 +268,7 @@ class VMwareHandler: vmware_manager = VMware.instance() vm = vmware_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"]) nio_type = request.json["type"] - if nio_type != "nio_udp": + if nio_type not in ("nio_udp", "nio_vmnet"): raise HTTPConflict(text="NIO of type {} is not supported".format(nio_type)) nio = vmware_manager.create_nio(None, request.json) yield from vm.adapter_add_nio_binding(int(request.match_info["adapter_number"]), nio) @@ -296,3 +296,21 @@ class VMwareHandler: vm = vmware_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"]) yield from vm.adapter_remove_nio_binding(int(request.match_info["adapter_number"])) response.set_status(204) + + @classmethod + @Route.post( + r"/projects/{project_id}/vmware/interfaces/vmnet", + parameters={ + "project_id": "The UUID of the project", + }, + status_codes={ + 201: "VMnet interface allocated", + }, + description="Allocate a VMware VMnet interface on the server") + def allocate_vmnet(request, response): + + vmware_manager = VMware.instance() + vmware_manager.refresh_vmnet_list() + vmnet = vmware_manager.allocate_vmnet() + response.set_status(201) + response.json({"vmnet": vmnet}) diff --git a/gns3server/modules/vmware/__init__.py b/gns3server/modules/vmware/__init__.py index a73b90c3..db32f917 100644 --- a/gns3server/modules/vmware/__init__.py +++ b/gns3server/modules/vmware/__init__.py @@ -37,6 +37,7 @@ log = logging.getLogger(__name__) from ..base_manager import BaseManager from .vmware_vm import VMwareVM from .vmware_error import VMwareError +from .nio_vmnet import NIOVMNET class VMware(BaseManager): @@ -230,7 +231,8 @@ class VMware(BaseManager): def allocate_vmnet(self): if not self._vmnets: - raise VMwareError("No more VMnet interfaces available") + raise VMwareError("No VMnet interface available between vmnet{} and vmnet{}".format(self._vmnet_start_range, + self._vmnet_end_range)) return self._vmnets.pop(0) def refresh_vmnet_list(self): @@ -251,6 +253,13 @@ class VMware(BaseManager): self._vmnets = vmnet_interfaces + def create_nio(self, executable, nio_settings): + + if nio_settings["type"] == "nio_vmnet": + return NIOVMNET(nio_settings["vmnet"]) + else: + return super().create_nio(None, nio_settings) + @property def host_type(self): """ @@ -501,6 +510,7 @@ class VMware(BaseManager): inventory_path = self.get_vmware_inventory_path() if os.path.exists(inventory_path): + #FIXME: inventory may exist if VMware workstation has not been fully uninstalled, therefore VMware player VMs are not searched return self._get_vms_from_inventory(inventory_path) else: # VMware player has no inventory file, let's search the default location for VMs. diff --git a/gns3server/modules/vmware/nio_vmnet.py b/gns3server/modules/vmware/nio_vmnet.py new file mode 100644 index 00000000..13e2c211 --- /dev/null +++ b/gns3server/modules/vmware/nio_vmnet.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2015 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 . + +""" +Interface for VMnet NIOs. +""" + +from ..nios.nio import NIO + + +class NIOVMNET(NIO): + + """ + VMnet NIO. + """ + + def __init__(self, vmnet): + + super().__init__() + self._vmnet = vmnet + + @property + def vmnet(self): + """ + Returns vmnet interface used by this NIO. + + :returns: vmnet interface name + """ + + return self._vmnet + + def __str__(self): + + return "NIO VMNET" + + def __json__(self): + + return {"type": "nio_vmnet", + "vmnet": self._vmnet} diff --git a/gns3server/modules/vmware/vmware_vm.py b/gns3server/modules/vmware/vmware_vm.py index aa19dbde..1aba1a3e 100644 --- a/gns3server/modules/vmware/vmware_vm.py +++ b/gns3server/modules/vmware/vmware_vm.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2014 GNS3 Technologies Inc. +# Copyright (C) 2015 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 @@ -33,6 +33,7 @@ from gns3server.utils.interfaces import get_windows_interfaces from collections import OrderedDict from .vmware_error import VMwareError from ..nios.nio_udp import NIOUDP +from .nio_vmnet import NIOVMNET from ..adapters.ethernet_adapter import EthernetAdapter from ..base_vm import BaseVM @@ -72,6 +73,7 @@ class VMwareVM(BaseVM): self._adapters = 0 self._ethernet_adapters = {} self._adapter_type = "e1000" + self._use_ubridge = True self._use_any_adapter = False if not os.path.exists(vmx_path): @@ -89,6 +91,7 @@ class VMwareVM(BaseVM): "enable_remote_console": self.enable_remote_console, "adapters": self._adapters, "adapter_type": self.adapter_type, + "use_ubridge": self.use_ubridge, "use_any_adapter": self.use_any_adapter, "vm_directory": self.working_dir} return json @@ -107,6 +110,26 @@ class VMwareVM(BaseVM): log.debug("Control VM '{}' result: {}".format(subcommand, result)) return result + def _read_vmx_file(self): + """ + Reads from the VMware VMX file corresponding to this VM. + """ + + try: + self._vmx_pairs = self.manager.parse_vmware_file(self._vmx_path) + except OSError as e: + raise VMwareError('Could not read VMware VMX file "{}": {}'.format(self._vmx_path, e)) + + def _write_vmx_file(self): + """ + Writes pairs to the VMware VMX file corresponding to this VM. + """ + + try: + self.manager.write_vmx_file(self._vmx_path, self._vmx_pairs) + except OSError as e: + raise VMwareError('Could not write VMware VMX file "{}": {}'.format(self._vmx_path, e)) + @asyncio.coroutine def create(self): """ @@ -190,41 +213,37 @@ class VMwareVM(BaseVM): Set up VMware networking. """ - # first do some sanity checks + # first some sanity checks for adapter_number in range(0, self._adapters): + + # we want the vmnet interface to be connected when starting the VM connected = "ethernet{}.startConnected".format(adapter_number) if self._get_vmx_setting(connected): del self._vmx_pairs[connected] - # check if any vmnet interface managed by GNS3 is being used on existing VMware adapters - if self._get_vmx_setting("ethernet{}.present".format(adapter_number), "TRUE"): - connection_type = "ethernet{}.connectiontype".format(adapter_number) - if connection_type in self._vmx_pairs and self._vmx_pairs[connection_type] in ("hostonly", "custom"): - vnet = "ethernet{}.vnet".format(adapter_number) - if vnet in self._vmx_pairs: - vmnet = os.path.basename(self._vmx_pairs[vnet]) - if self.manager.is_managed_vmnet(vmnet): - raise VMwareError("Network adapter {} is already associated with VMnet interface {} which is managed by GNS3, please remove".format(adapter_number, vmnet)) - # check for adapter type if self._adapter_type != "default": adapter_type = "ethernet{}.virtualdev".format(adapter_number) if adapter_type in self._vmx_pairs and self._vmx_pairs[adapter_type] != self._adapter_type: - raise VMwareError("Network adapter {} is not of type {}, please fix or remove it".format(adapter_number, self._adapter_type)) + raise VMwareError("Existing VMware network adapter {} is not of type {}, please fix or set adapter type to default in GNS3".format(adapter_number, + self._adapter_type)) - # check if connected to an adapter configured for nat or bridge - if self._ethernet_adapters[adapter_number].get_nio(0) and not self._use_any_adapter: - if self._get_vmx_setting("ethernet{}.present".format(adapter_number), "TRUE"): - # check for the connection type - connection_type = "ethernet{}.connectiontype".format(adapter_number) - if connection_type in self._vmx_pairs and self._vmx_pairs[connection_type] in ("nat", "bridged", "hostonly"): - raise VMwareError("Attachment ({}) already configured on network adapter {}. " - "Please remove it or allow GNS3 to use any adapter.".format(self._vmx_pairs[connection_type], - adapter_number)) + # # check if any vmnet interface managed by GNS3 is being used on existing VMware adapters + # if self._get_vmx_setting("ethernet{}.present".format(adapter_number), "TRUE"): + # connection_type = "ethernet{}.connectiontype".format(adapter_number) + # if connection_type in self._vmx_pairs and self._vmx_pairs[connection_type] in ("hostonly", "custom"): + # vnet = "ethernet{}.vnet".format(adapter_number) + # if vnet in self._vmx_pairs: + # vmnet = os.path.basename(self._vmx_pairs[vnet]) + # #nio = self._ethernet_adapters[adapter_number].get_nio(0) + # if self.manager.is_managed_vmnet(vmnet): + # raise VMwareError("Network adapter {} is already associated with VMnet interface {} which is managed by GNS3, please remove".format(adapter_number, vmnet)) - # now configure VMware network adapters + # then configure VMware network adapters self.manager.refresh_vmnet_list() for adapter_number in range(0, self._adapters): + + # add/update the interface ethernet_adapter = {"ethernet{}.present".format(adapter_number): "TRUE", "ethernet{}.addresstype".format(adapter_number): "generated", "ethernet{}.generatedaddressoffset".format(adapter_number): "0"} @@ -235,18 +254,35 @@ class VMwareVM(BaseVM): connection_type = "ethernet{}.connectiontype".format(adapter_number) if not self._use_any_adapter and connection_type in self._vmx_pairs and self._vmx_pairs[connection_type] in ("nat", "bridged", "hostonly"): continue - - vnet = "ethernet{}.vnet".format(adapter_number) - if vnet in self._vmx_pairs: - vmnet = os.path.basename(self._vmx_pairs[vnet]) - else: - try: - vmnet = self.manager.allocate_vmnet() - finally: - self._vmnets.clear() - self._vmnets.append(vmnet) self._vmx_pairs["ethernet{}.connectiontype".format(adapter_number)] = "custom" - self._vmx_pairs["ethernet{}.vnet".format(adapter_number)] = vmnet + + if self._use_ubridge: + # make sure we have a vmnet per adapter if we use uBridge + allocate_vmnet = False + + # first check if a vmnet is already assigned to the adapter + vnet = "ethernet{}.vnet".format(adapter_number) + if vnet in self._vmx_pairs: + vmnet = os.path.basename(self._vmx_pairs[vnet]) + if vmnet in self.manager.is_managed_vmnet(vmnet): + # vmnet already managed, try to allocate a new one + allocate_vmnet = True + else: + # otherwise allocate a new one + allocate_vmnet = True + + if allocate_vmnet: + try: + vmnet = self.manager.allocate_vmnet() + except: + # clear everything up in case of error (e.g. no enough vmnets) + self._vmnets.clear() + raise + + # mark the vmnet managed by us + if not vmnet in self._vmnets: + self._vmnets.append(vmnet) + self._vmx_pairs["ethernet{}.vnet".format(adapter_number)] = vmnet # disable remaining network adapters for adapter_number in range(self._adapters, self._maximum_adapters): @@ -350,11 +386,7 @@ class VMwareVM(BaseVM): :returns: boolean """ - try: - self._vmx_pairs = self.manager.parse_vmware_file(self._vmx_path) - except OSError as e: - raise VMwareError('Could not read VMware VMX file "{}": {}'.format(self._vmx_path, e)) - + self._read_vmx_file() if self._get_vmx_setting("vhv.enable", "TRUE"): return True return False @@ -372,30 +404,24 @@ class VMwareVM(BaseVM): if not ubridge_path or not os.path.isfile(ubridge_path): raise VMwareError("ubridge is necessary to start a VMware VM") - yield from self._start_ubridge() - - try: - self._vmx_pairs = self.manager.parse_vmware_file(self._vmx_path) - except OSError as e: - raise VMwareError('Could not read VMware VMX file "{}": {}'.format(self._vmx_path, e)) + if self._use_ubridge: + yield from self._start_ubridge() + self._read_vmx_file() self._set_network_options() self._set_serial_console() - - try: - self.manager.write_vmx_file(self._vmx_path, self._vmx_pairs) - except OSError as e: - raise VMwareError('Could not write VMware VMX file "{}": {}'.format(self._vmx_path, e)) + self._write_vmx_file() if self._headless: yield from self._control_vm("start", "nogui") else: yield from self._control_vm("start") - for adapter_number in range(0, self._adapters): - nio = self._ethernet_adapters[adapter_number].get_nio(0) - if nio: - yield from self._add_ubridge_connection(nio, adapter_number) + if self._use_ubridge: + for adapter_number in range(0, self._adapters): + nio = self._ethernet_adapters[adapter_number].get_nio(0) + if nio: + yield from self._add_ubridge_connection(nio, adapter_number) if self._enable_remote_console and self._console is not None: yield from asyncio.sleep(1) # give some time to VMware to create the pipe file. @@ -426,36 +452,30 @@ class VMwareVM(BaseVM): yield from self._control_vm("stop") finally: self._started = False - self._vmnets.clear() - try: - self._vmx_pairs = self.manager.parse_vmware_file(self._vmx_path) - except OSError as e: - raise VMwareError('Could not read VMware VMX file "{}": {}'.format(self._vmx_path, e)) - # remove the adapters managed by GNS3 - for adapter_number in range(0, self._adapters): - if self._get_vmx_setting("ethernet{}.vnet".format(adapter_number)) or \ - self._get_vmx_setting("ethernet{}.connectiontype".format(adapter_number)) is None: - vnet = "ethernet{}.vnet".format(adapter_number) - if vnet in self._vmx_pairs: - vmnet = os.path.basename(self._vmx_pairs[vnet]) - if not self.manager.is_managed_vmnet(vmnet): - continue - log.debug("removing adapter {}".format(adapter_number)) - for key in self._vmx_pairs.keys(): - if key.startswith("ethernet{}.".format(adapter_number)): - del self._vmx_pairs[key] + self._read_vmx_file() + if self._use_ubridge: + self._vmnets.clear() + # remove the adapters managed by GNS3 + for adapter_number in range(0, self._adapters): + if self._get_vmx_setting("ethernet{}.vnet".format(adapter_number)) or \ + self._get_vmx_setting("ethernet{}.connectiontype".format(adapter_number)) is None: + vnet = "ethernet{}.vnet".format(adapter_number) + if vnet in self._vmx_pairs: + vmnet = os.path.basename(self._vmx_pairs[vnet]) + if not self.manager.is_managed_vmnet(vmnet): + continue + log.debug("removing adapter {}".format(adapter_number)) + for key in self._vmx_pairs.keys(): + if key.startswith("ethernet{}.".format(adapter_number)): + del self._vmx_pairs[key] # re-enable any remaining network adapters for adapter_number in range(self._adapters, self._maximum_adapters): if self._get_vmx_setting("ethernet{}.present".format(adapter_number), "TRUE"): log.debug("enabling remaining adapter {}".format(adapter_number)) self._vmx_pairs["ethernet{}.startconnected".format(adapter_number)] = "TRUE" - - try: - self.manager.write_vmx_file(self._vmx_path, self._vmx_pairs) - except OSError as e: - raise VMwareError('Could not write VMware VMX file "{}": {}'.format(self._vmx_path, e)) + self._write_vmx_file() log.info("VMware VM '{name}' [{id}] stopped".format(name=self.name, id=self.id)) @@ -493,7 +513,7 @@ class VMwareVM(BaseVM): @asyncio.coroutine def close(self): """ - Closes this VirtualBox VM. + Closes this VMware VM. """ if self._closed: @@ -510,7 +530,8 @@ class VMwareVM(BaseVM): for nio in adapter.ports.values(): if nio and isinstance(nio, NIOUDP): self.manager.port_manager.release_udp_port(nio.lport, self._project) - + if nio and isinstance(nio, NIOVMNET) and nio.vmnet in self._vmnets: + self._vmnets.remove(nio.vmnet) try: self.acpi_shutdown = False yield from self.stop() @@ -697,6 +718,30 @@ class VMwareVM(BaseVM): id=self.id, adapter_type=adapter_type)) + @property + def use_ubridge(self): + """ + Returns either GNS3 can use uBridge for network connections. + + :returns: boolean + """ + + return self._use_ubridge + + @use_ubridge.setter + def use_ubridge(self, use_ubridge): + """ + Allows GNS3 to use uBridge for network connections. + + :param use_ubridge: boolean + """ + + if use_ubridge: + log.info("VMware VM '{name}' [{id}] will use uBridge for network connections".format(name=self.name, id=self.id)) + else: + log.info("VMware VM '{name}' [{id}] will not use uBridge for network connections".format(name=self.name, id=self.id)) + self._use_ubridge = use_ubridge + @property def use_any_adapter(self): """ @@ -736,8 +781,25 @@ class VMwareVM(BaseVM): raise VMwareError("Adapter {adapter_number} doesn't exist on VMware VM '{name}'".format(name=self.name, adapter_number=adapter_number)) + self._read_vmx_file() + # check if trying to connect to a nat, bridged or host-only adapter + if self._ethernet_adapters[adapter_number].get_nio(0) and not self._use_any_adapter: + if self._get_vmx_setting("ethernet{}.present".format(adapter_number), "TRUE"): + # check for the connection type + connection_type = "ethernet{}.connectiontype".format(adapter_number) + if connection_type in self._vmx_pairs and self._vmx_pairs[connection_type] in ("nat", "bridged", "hostonly"): + raise VMwareError("Attachment ({}) already configured on network adapter {}. " + "Please remove it or allow GNS3 to use any adapter.".format(self._vmx_pairs[connection_type], + adapter_number)) + + if isinstance(nio, NIOVMNET): + if self._started: + raise VMwareError("Sorry, adding a link to a started VMware VM is not supported without uBridge enabled") + self._vmx_pairs["ethernet{}.vnet".format(adapter_number)] = nio.vmnet + self._write_vmx_file() + self._vmnets.append(nio.vmnet) adapter.add_nio(0, nio) - if self._started: + if self._started and self._use_ubridge: yield from self._add_ubridge_connection(nio, adapter_number) log.info("VMware VM '{name}' [{id}]: {nio} added to adapter {adapter_number}".format(name=self.name, @@ -765,8 +827,16 @@ class VMwareVM(BaseVM): nio = adapter.get_nio(0) if isinstance(nio, NIOUDP): self.manager.port_manager.release_udp_port(nio.lport, self._project) + if isinstance(nio, NIOVMNET): + self._read_vmx_file() + vnet = "ethernet{}.vnet".format(adapter_number) + if vnet in self._vmx_pairs: + del self._vmx_pairs[vnet] + self._write_vmx_file() + if nio.vmnet in self._vmnets: + self._vmnets.remove(nio.vmnet) adapter.remove_nio(0) - if self._started: + if self._started and self._use_ubridge: yield from self._delete_ubridge_connection(adapter_number) log.info("VMware VM '{name}' [{id}]: {nio} removed from adapter {adapter_number}".format(name=self.name, diff --git a/gns3server/schemas/nio.py b/gns3server/schemas/nio.py index ca701314..ead1287f 100644 --- a/gns3server/schemas/nio.py +++ b/gns3server/schemas/nio.py @@ -143,6 +143,21 @@ NIO_SCHEMA = { "required": ["type", "control_file", "local_file"], "additionalProperties": False }, + "VMNET": { + "description": "VMNET Network Input/Output", + "properties": { + "type": { + "enum": ["nio_vmnet"] + }, + "vmnet": { + "description": "VMnet interface name e.g. vmnet12", + "type": "string", + "minLength": 1 + }, + }, + "required": ["type", "vmnet"], + "additionalProperties": False + }, "NULL": { "description": "NULL Network Input/Output", "properties": { @@ -162,6 +177,7 @@ NIO_SCHEMA = { {"$ref": "#/definitions/TAP"}, {"$ref": "#/definitions/UNIX"}, {"$ref": "#/definitions/VDE"}, + {"$ref": "#/definitions/VMNET"}, {"$ref": "#/definitions/NULL"}, ], "additionalProperties": True, diff --git a/gns3server/schemas/vmware.py b/gns3server/schemas/vmware.py index 84bd6e60..29343e37 100644 --- a/gns3server/schemas/vmware.py +++ b/gns3server/schemas/vmware.py @@ -71,6 +71,10 @@ VMWARE_CREATE_SCHEMA = { "type": "string", "minLength": 1, }, + "use_ubridge": { + "description": "use uBridge for network connections", + "type": "boolean", + }, "use_any_adapter": { "description": "allow GNS3 to use any VMware adapter", "type": "boolean", @@ -124,6 +128,10 @@ VMWARE_UPDATE_SCHEMA = { "type": "string", "minLength": 1, }, + "use_ubridge": { + "description": "use uBridge for network connections", + "type": "boolean", + }, "use_any_adapter": { "description": "allow GNS3 to use any VMware adapter", "type": "boolean", @@ -188,6 +196,10 @@ VMWARE_OBJECT_SCHEMA = { "type": "string", "minLength": 1, }, + "use_ubridge": { + "description": "use uBridge for network connections", + "type": "boolean", + }, "use_any_adapter": { "description": "allow GNS3 to use any VMware adapter", "type": "boolean",