diff --git a/gns3server/controller/base_gns3_vm.py b/gns3server/controller/base_gns3_vm.py
new file mode 100644
index 00000000..8afbeb09
--- /dev/null
+++ b/gns3server/controller/base_gns3_vm.py
@@ -0,0 +1,170 @@
+#!/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 .
+
+import asyncio
+
+
+class BaseGNS3VM:
+
+ def __init__(self, vmname, port):
+
+ self._vmname = vmname
+ self._ip_address = None
+ self._port = port
+ self._headless = False
+ self._running = False
+
+ @property
+ def vmname(self):
+ """
+ Returns the GNS3 VM name.
+
+ :returns: VM name
+ """
+
+ return self._vmname
+
+ @vmname.setter
+ def vmname(self, new_name):
+ """
+ Sets the GNS3 VM name
+
+ :param new_name: new VM name
+ """
+
+ self._vmname = new_name
+
+ @property
+ def port(self):
+ """
+ Returns the GNS3 VM port.
+
+ :returns: VM port
+ """
+
+ return self._port
+
+ @port.setter
+ def port(self, new_port):
+ """
+ Sets the GNS3 VM port
+
+ :param new_port: new VM port
+ """
+
+ self._port = new_port
+
+ @property
+ def ip_address(self):
+ """
+ Returns the GNS3 VM IP address.
+
+ :returns: VM IP address
+ """
+
+ return self._ip_address
+
+ @ip_address.setter
+ def ip_address(self, new_ip_address):
+ """
+ Sets the GNS3 VM IP address.
+
+ :param new_ip_address: new VM IP address
+ """
+
+ self._ip_address = new_ip_address
+
+ @property
+ def running(self):
+ """
+ Returns whether the GNS3 VM is running or not.
+
+ :returns: boolean
+ """
+
+ return self._running
+
+ @running.setter
+ def running(self, value):
+ """
+ Sets whether the GNS3 VM is running or not.
+
+ :param value: boolean
+ """
+
+ self._running = value
+
+ @property
+ def headless(self):
+ """
+ Returns whether the GNS3 VM is headless or not.
+
+ :returns: boolean
+ """
+
+ return self._headless
+
+ @headless.setter
+ def headless(self, value):
+ """
+ Sets whether the GNS3 VM is headless or not.
+
+ :param value: boolean
+ """
+
+ self._headless = value
+
+ @asyncio.coroutine
+ def start(self):
+ """
+ Starts the GNS3 VM.
+ """
+
+ raise NotImplementedError
+
+
+ @asyncio.coroutine
+ def stop(self, force=False):
+ """
+ Stops the GNS3 VM.
+ """
+
+ raise NotImplementedError
+
+ @asyncio.coroutine
+ def set_vcpus(self, vcpus):
+ """
+ Set the number of vCPU cores for the GNS3 VM.
+
+ :param vcpus: number of vCPU cores
+
+ :returns: boolean
+ """
+
+ raise NotImplementedError
+
+ @asyncio.coroutine
+ def set_ram(self, ram):
+ """
+ Set the RAM amount for the GNS3 VM.
+
+ :param ram: amount of memory
+
+ :returns: boolean
+ """
+
+ raise NotImplementedError
diff --git a/gns3server/controller/gns3_vm_error.py b/gns3server/controller/gns3_vm_error.py
new file mode 100644
index 00000000..29e4f85b
--- /dev/null
+++ b/gns3server/controller/gns3_vm_error.py
@@ -0,0 +1,29 @@
+#!/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 .
+
+
+class GNS3VMError(Exception):
+
+ def __init__(self, message):
+ super().__init__(message)
+ self._message = message
+
+ def __repr__(self):
+ return self._message
+
+ def __str__(self):
+ return self._message
diff --git a/gns3server/controller/virtualbox_gns3_vm.py b/gns3server/controller/virtualbox_gns3_vm.py
new file mode 100644
index 00000000..96045d75
--- /dev/null
+++ b/gns3server/controller/virtualbox_gns3_vm.py
@@ -0,0 +1,253 @@
+#!/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 .
+
+import logging
+import asyncio
+import socket
+
+from .base_gns3_vm import BaseGNS3VM
+from .gns3_vm_error import GNS3VMError
+
+from ..compute.virtualbox import (
+ VirtualBox,
+ VirtualBoxError
+)
+
+log = logging.getLogger(__name__)
+
+
+class VirtualBoxGNS3VM(BaseGNS3VM):
+
+ def __init__(self, vmname, port):
+
+ super().__init__(vmname, port)
+ self._virtualbox_manager = VirtualBox()
+
+ @asyncio.coroutine
+ def _execute(self, subcommand, args, timeout=60):
+
+ try:
+ return (yield from self._virtualbox_manager.execute(subcommand, args, timeout))
+ except VirtualBoxError as e:
+ raise GNS3VMError("Error while executing VBoxManage command: {}".format(e))
+
+ @asyncio.coroutine
+ def _get_state(self):
+ """
+ Returns the VM state (e.g. running, paused etc.)
+
+ :returns: state (string)
+ """
+
+ result = yield from self._execute("showvminfo", [self._vmname, "--machinereadable"])
+ for info in result.splitlines():
+ if '=' in info:
+ name, value = info.split('=', 1)
+ if name == "VMState":
+ return value.strip('"')
+ return "unknown"
+
+ @asyncio.coroutine
+ def _look_for_interface(self, network_backend):
+ """
+ Look for an interface with a specific network backend.
+
+ :returns: interface number or -1 if none is found
+ """
+
+ result = yield from self._execute("showvminfo", [self._vmname, "--machinereadable"])
+ interface = -1
+ for info in result.splitlines():
+ if '=' in info:
+ name, value = info.split('=', 1)
+ if name.startswith("nic") and value.strip('"') == network_backend:
+ try:
+ interface = int(name[3:])
+ break
+ except ValueError:
+ continue
+ return interface
+
+ @asyncio.coroutine
+ def _look_for_vboxnet(self, interface_number):
+ """
+ Look for the VirtualBox network name associated with a host only interface.
+
+ :returns: None or vboxnet name
+ """
+
+ result = yield from self._execute("showvminfo", [self._vmname, "--machinereadable"])
+ for info in result.splitlines():
+ if '=' in info:
+ name, value = info.split('=', 1)
+ if name == "hostonlyadapter{}".format(interface_number):
+ return value.strip('"')
+ return None
+
+ @asyncio.coroutine
+ def _check_dhcp_server(self, vboxnet):
+ """
+ Check if the DHCP server associated with a vboxnet is enabled.
+
+ :param vboxnet: vboxnet name
+
+ :returns: boolean
+ """
+
+ properties = yield from self._execute("list", ["dhcpservers"])
+ flag_dhcp_server_found = False
+ for prop in properties.splitlines():
+ try:
+ name, value = prop.split(':', 1)
+ except ValueError:
+ continue
+ if name.strip() == "NetworkName" and value.strip().endswith(vboxnet):
+ flag_dhcp_server_found = True
+ if flag_dhcp_server_found and name.strip() == "Enabled":
+ if value.strip() == "Yes":
+ return True
+ return False
+
+ @asyncio.coroutine
+ def _check_vbox_port_forwarding(self):
+ """
+ Checks if the NAT port forwarding rule exists.
+
+ :returns: boolean
+ """
+
+ result = yield from self._execute("showvminfo", [self._vmname, "--machinereadable"])
+ for info in result.splitlines():
+ if '=' in info:
+ name, value = info.split('=', 1)
+ if name.startswith("Forwarding") and value.strip('"').startswith("GNS3VM"):
+ return True
+ return False
+
+ @asyncio.coroutine
+ def start(self):
+ """
+ Start the GNS3 VM.
+ """
+
+ # get a NAT interface number
+ nat_interface_number = yield from self._look_for_interface("nat")
+ if nat_interface_number < 0:
+ raise GNS3VMError("The GNS3 VM must have a NAT interface configured in order to start")
+
+ hostonly_interface_number = yield from self._look_for_interface("hostonly")
+ if hostonly_interface_number < 0:
+ raise GNS3VMError("The GNS3 VM must have a host only interface configured in order to start")
+
+ vboxnet = yield from self._look_for_vboxnet(hostonly_interface_number)
+ if vboxnet is None:
+ raise GNS3VMError("VirtualBox host-only network could not be found for interface {}".format(hostonly_interface_number))
+
+ if not (yield from self._check_dhcp_server(vboxnet)):
+ raise GNS3VMError("DHCP must be enabled on VirtualBox host-only network: {}".format(vboxnet))
+
+ vm_state = yield from self._get_vbox_vm_state()
+ log.info('"{}" state is {}'.format(self._vmname, vm_state))
+ if vm_state in ("poweroff", "saved"):
+ # start the VM if it is not running
+ args = [self._vmname]
+ if self._headless:
+ args.extend(["--type", "headless"])
+ yield from self._execute("startvm", args)
+ log.info("GNS3 VM has been started")
+ self.running = True
+
+ ip_address = "127.0.0.1"
+ try:
+ # get a random port on localhost
+ with socket.socket() as s:
+ s.bind((ip_address, 0))
+ port = s.getsockname()[1]
+ except OSError as e:
+ raise GNS3VMError("Error while getting random port: {}".format(e))
+
+ if (yield from self._check_vbox_port_forwarding()):
+ # delete the GNS3VM NAT port forwarding rule if it exists
+ log.info("Removing GNS3VM NAT port forwarding rule from interface {}".format(nat_interface_number))
+ yield from self._execute("controlvm", [self._vmname, "natpf{}".format(nat_interface_number), "delete", "GNS3VM"])
+
+ # add a GNS3VM NAT port forwarding rule to redirect 127.0.0.1 with random port to port 3080 in the VM
+ log.info("Adding GNS3VM NAT port forwarding rule with port {} to interface {}".format(port, nat_interface_number))
+ yield from self._execute("controlvm", [self._vmname, "natpf{}".format(nat_interface_number),
+ "GNS3VM,tcp,{},{},,3080".format(ip_address, port)])
+
+ original_port = self.port
+ self.port = port
+
+ # TODO: retrieve interfaces on server
+ # # ask the server all a list of all its interfaces along with IP addresses
+ # status, json_data = self._waitForServer(vm_server, "interfaces", retry=120)
+ # if status == 401:
+ # self.error.emit("Wrong user or password for the GNS3 VM".format(status), True)
+ # return False
+ # if status != 200:
+ # msg = "Server {} has replied with status code {} when retrieving the network interfaces".format(
+ # vm_server.url(), status)
+ # log.error(msg)
+ # self.error.emit(msg, True)
+ # return False
+
+ # find the ip address for the first hostonly interface
+ hostonly_ip_address_found = False
+ for interface in json_data:
+ if "name" in interface and interface["name"] == "eth{}".format(hostonly_interface_number - 1):
+ if "ip_address" in interface:
+ self.ip_address = interface["ip_address"]
+ self.port = original_port
+ log.info("GNS3 VM IP address set to {}".format(interface["ip_address"]))
+ hostonly_ip_address_found = True
+ break
+
+ if not hostonly_ip_address_found:
+ raise GNS3VMError("Not IP address could be found in the GNS3 VM for eth{}".format(hostonly_interface_number - 1))
+
+ @asyncio.coroutine
+ def stop(self, force=False):
+ """
+ Stops the GNS3 VM.
+ """
+
+ yield from self._execute("controlvm", [self._vmname, "acpipowerbutton"], timeout=3)
+ log.info("GNS3 VM hsd been stopped")
+ self.running = False
+
+ @asyncio.coroutine
+ def set_vcpus(self, vcpus):
+ """
+ Set the number of vCPU cores for the GNS3 VM.
+
+ :param vcpus: number of vCPU cores
+ """
+
+ yield from self.execute("modifyvm", [self._vmname, "--cpus", str(vcpus)], timeout=3)
+ log.info("GNS3 VM vCPU count set to {}".format(vcpus))
+
+ @asyncio.coroutine
+ def set_ram(self, ram):
+ """
+ Set the RAM amount for the GNS3 VM.
+
+ :param ram: amount of memory
+ """
+
+ yield from self._execute("modifyvm", [self._vmname, "--memory", str(ram)], timeout=3)
+ log.info("GNS3 VM RAM amount set to {}".format(ram))
diff --git a/gns3server/controller/vmware_gns3_vm.py b/gns3server/controller/vmware_gns3_vm.py
new file mode 100644
index 00000000..80beaff6
--- /dev/null
+++ b/gns3server/controller/vmware_gns3_vm.py
@@ -0,0 +1,120 @@
+#!/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 .
+
+import os
+import logging
+import asyncio
+
+from gns3server.compute.vmware import (
+ VMware,
+ VMwareError
+)
+
+from .base_gns3_vm import BaseGNS3VM
+from .gns3_vm_error import GNS3VMError
+log = logging.getLogger(__name__)
+
+
+class VMwareGNS3VM(BaseGNS3VM):
+
+ def __init__(self, vmname, port, vmx_path=None):
+
+ super().__init__(vmname, port)
+ self._vmx_path = vmx_path
+ self._vmware_manager = VMware()
+
+ @asyncio.coroutine
+ def _execute(self, subcommand, args, timeout=60):
+
+ try:
+ return (yield from self._vmware_manager.execute(subcommand, args, timeout))
+ except VMwareError as e:
+ raise GNS3VMError("Error while executing VMware command: {}".format(e))
+
+ @asyncio.coroutine
+ def start(self):
+ """
+ Starts the GNS3 VM.
+ """
+
+ # check we have a valid VMX file path
+ if not self._vmx_path:
+ raise GNS3VMError("GNS3 VM is not configured", True)
+ if not os.path.exists(self._vmx_path):
+ raise GNS3VMError("VMware VMX file {} doesn't exist".format(self._vmx_path), True)
+
+ # start the VM
+ args = [self._vmx_path]
+ if self._headless:
+ args.extend(["nogui"])
+ yield from self._execute("start", args)
+ log.info("GNS3 VM has been started")
+ self.running = True
+
+ # check if the VMware guest tools are installed
+ vmware_tools_state = yield from self._execute("checkToolsState", [self._vmx_path])
+ if vmware_tools_state not in ("installed", "running"):
+ raise GNS3VMError("VMware tools are not installed in {}".format(self.vmname))
+
+ # get the guest IP address (first adapter only)
+ guest_ip_address = yield from self._execute("getGuestIPAddress", [self._vmx_path, "-wait"], timeout=120)
+ self.ip_address = guest_ip_address
+ log.info("GNS3 VM IP address set to {}".format(guest_ip_address))
+
+ @asyncio.coroutine
+ def stop(self, force=False):
+ """
+ Stops the GNS3 VM.
+ """
+
+ if self._vmx_path is None:
+ raise GNS3VMError("No vm path configured, can't stop the VM")
+ yield from self._execute("stop", [self._vmx_path, "soft"])
+ log.info("GNS3 VM has been stopped")
+ self.running = False
+
+ @asyncio.coroutine
+ def set_vcpus(self, vcpus):
+ """
+ Set the number of vCPU cores for the GNS3 VM.
+
+ :param vcpus: number of vCPU cores
+ """
+
+ try:
+ pairs = VMware.parse_vmware_file(self._vmx_path)
+ pairs["numvcpus"] = str(vcpus)
+ VMware.write_vmx_file(self._vmx_path, pairs)
+ log.info("GNS3 VM vCPU count set to {}".format(vcpus))
+ except OSError as e:
+ raise GNS3VMError('Could not read/write VMware VMX file "{}": {}'.format(self._vmx_path, e))
+
+ @asyncio.coroutine
+ def set_ram(self, ram):
+ """
+ Set the RAM amount for the GNS3 VM.
+
+ :param ram: amount of memory
+ """
+
+ try:
+ pairs = VMware.parse_vmware_file(self._vmx_path)
+ pairs["memsize"] = str(ram)
+ VMware.write_vmx_file(self._vmx_path, pairs)
+ log.info("GNS3 VM RAM amount set to {}".format(ram))
+ except OSError as e:
+ raise GNS3VMError('Could not read/write VMware VMX file "{}": {}'.format(self._vmx_path, e))