diff --git a/gns3server/handlers/__init__.py b/gns3server/handlers/__init__.py
index 842bb960..68297c25 100644
--- a/gns3server/handlers/__init__.py
+++ b/gns3server/handlers/__init__.py
@@ -25,6 +25,7 @@ from gns3server.handlers.api.dynamips_vm_handler import DynamipsVMHandler
from gns3server.handlers.api.qemu_handler import QEMUHandler
from gns3server.handlers.api.virtualbox_handler import VirtualBoxHandler
from gns3server.handlers.api.vpcs_handler import VPCSHandler
+from gns3server.handlers.api.vmware_handler import VMwareHandler
from gns3server.handlers.api.config_handler import ConfigHandler
from gns3server.handlers.api.server_handler import ServerHandler
from gns3server.handlers.api.file_handler import FileHandler
diff --git a/gns3server/handlers/api/virtualbox_handler.py b/gns3server/handlers/api/virtualbox_handler.py
index 17d6ce66..f948fbe4 100644
--- a/gns3server/handlers/api/virtualbox_handler.py
+++ b/gns3server/handlers/api/virtualbox_handler.py
@@ -246,7 +246,7 @@ class VirtualBoxHandler:
404: "Instance doesn't exist"
},
description="Resume a suspended VirtualBox VM instance")
- def suspend(request, response):
+ def resume(request, response):
vbox_manager = VirtualBox.instance()
vm = vbox_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
diff --git a/gns3server/handlers/api/vmware_handler.py b/gns3server/handlers/api/vmware_handler.py
new file mode 100644
index 00000000..e8bf35e0
--- /dev/null
+++ b/gns3server/handlers/api/vmware_handler.py
@@ -0,0 +1,229 @@
+# -*- 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 .
+
+from ...web.route import Route
+from ...schemas.vmware import VMWARE_CREATE_SCHEMA
+from ...schemas.vmware import VMWARE_UPDATE_SCHEMA
+from ...schemas.vmware import VMWARE_OBJECT_SCHEMA
+from ...modules.vmware import VMware
+from ...modules.project_manager import ProjectManager
+
+
+class VMwareHandler:
+
+ """
+ API entry points for VMware.
+ """
+
+ @classmethod
+ @Route.post(
+ r"/projects/{project_id}/vmware/vms",
+ parameters={
+ "project_id": "UUID for the project"
+ },
+ status_codes={
+ 201: "Instance created",
+ 400: "Invalid request",
+ 409: "Conflict"
+ },
+ description="Create a new VMware VM instance",
+ input=VMWARE_CREATE_SCHEMA,
+ output=VMWARE_OBJECT_SCHEMA)
+ def create(request, response):
+
+ vmware_manager = VMware.instance()
+ vm = yield from vmware_manager.create_vm(request.json.pop("name"),
+ request.match_info["project_id"],
+ request.json.get("vm_id"),
+ request.json.pop("vmx_path"),
+ request.json.pop("linked_clone"),
+ console=request.json.get("console", None))
+
+ # for name, value in request.json.items():
+ # if name != "vm_id":
+ # if hasattr(vm, name) and getattr(vm, name) != value:
+ # setattr(vm, name, value)
+
+ response.set_status(201)
+ response.json(vm)
+
+ @classmethod
+ @Route.get(
+ r"/projects/{project_id}/vmware/vms/{vm_id}",
+ parameters={
+ "project_id": "UUID for the project",
+ "vm_id": "UUID for the instance"
+ },
+ status_codes={
+ 200: "Success",
+ 400: "Invalid request",
+ 404: "Instance doesn't exist"
+ },
+ description="Get a VMware VM instance",
+ output=VMWARE_OBJECT_SCHEMA)
+ def show(request, response):
+
+ vmware_manager = VMware.instance()
+ vm = vmware_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
+ response.json(vm)
+
+ @classmethod
+ @Route.put(
+ r"/projects/{project_id}/vmware/vms/{vm_id}",
+ parameters={
+ "project_id": "UUID for the project",
+ "vm_id": "UUID for the instance"
+ },
+ status_codes={
+ 200: "Instance updated",
+ 400: "Invalid request",
+ 404: "Instance doesn't exist",
+ 409: "Conflict"
+ },
+ description="Update a VMware VM instance",
+ input=VMWARE_UPDATE_SCHEMA,
+ output=VMWARE_OBJECT_SCHEMA)
+ def update(request, response):
+
+ vmware_manager = VMware.instance()
+ vm = vmware_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
+
+ # for name, value in request.json.items():
+ # if hasattr(vm, name) and getattr(vm, name) != value:
+ # setattr(vm, name, value)
+
+ response.json(vm)
+
+ @classmethod
+ @Route.delete(
+ r"/projects/{project_id}/vmware/vms/{vm_id}",
+ parameters={
+ "project_id": "UUID for the project",
+ "vm_id": "UUID for the instance"
+ },
+ status_codes={
+ 204: "Instance deleted",
+ 400: "Invalid request",
+ 404: "Instance doesn't exist"
+ },
+ description="Delete a VMware VM instance")
+ def delete(request, response):
+
+ # check the project_id exists
+ ProjectManager.instance().get_project(request.match_info["project_id"])
+ yield from VMware.instance().delete_vm(request.match_info["vm_id"])
+ response.set_status(204)
+
+ @classmethod
+ @Route.post(
+ r"/projects/{project_id}/vmware/vms/{vm_id}/start",
+ parameters={
+ "project_id": "UUID for the project",
+ "vm_id": "UUID for the instance"
+ },
+ status_codes={
+ 204: "Instance started",
+ 400: "Invalid request",
+ 404: "Instance doesn't exist"
+ },
+ description="Start a VMware VM instance")
+ def start(request, response):
+
+ vmware_manager = VMware.instance()
+ vm = vmware_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
+ yield from vm.start()
+ response.set_status(204)
+
+ @classmethod
+ @Route.post(
+ r"/projects/{project_id}/vmware/vms/{vm_id}/stop",
+ parameters={
+ "project_id": "UUID for the project",
+ "vm_id": "UUID for the instance"
+ },
+ status_codes={
+ 204: "Instance stopped",
+ 400: "Invalid request",
+ 404: "Instance doesn't exist"
+ },
+ description="Stop a VMware VM instance")
+ def stop(request, response):
+
+ vmware_manager = VMware.instance()
+ vm = vmware_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
+ yield from vm.stop()
+ response.set_status(204)
+
+ @classmethod
+ @Route.post(
+ r"/projects/{project_id}/vmware/vms/{vm_id}/suspend",
+ parameters={
+ "project_id": "UUID for the project",
+ "vm_id": "UUID for the instance"
+ },
+ status_codes={
+ 204: "Instance suspended",
+ 400: "Invalid request",
+ 404: "Instance doesn't exist"
+ },
+ description="Suspend a VMware VM instance")
+ def suspend(request, response):
+
+ vmware_manager = VMware.instance()
+ vm = vmware_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
+ yield from vm.suspend()
+ response.set_status(204)
+
+ @classmethod
+ @Route.post(
+ r"/projects/{project_id}/vmware/vms/{vm_id}/resume",
+ parameters={
+ "project_id": "UUID for the project",
+ "vm_id": "UUID for the instance"
+ },
+ status_codes={
+ 204: "Instance resumed",
+ 400: "Invalid request",
+ 404: "Instance doesn't exist"
+ },
+ description="Resume a suspended VMware VM instance")
+ def resume(request, response):
+
+ vmware_manager = VMware.instance()
+ vm = vmware_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
+ yield from vm.resume()
+ response.set_status(204)
+
+ @classmethod
+ @Route.post(
+ r"/projects/{project_id}/vmware/vms/{vm_id}/reload",
+ parameters={
+ "project_id": "UUID for the project",
+ "vm_id": "UUID for the instance"
+ },
+ status_codes={
+ 204: "Instance reloaded",
+ 400: "Invalid request",
+ 404: "Instance doesn't exist"
+ },
+ description="Reload a VMware VM instance")
+ def reload(request, response):
+
+ vmware_manager = VMware.instance()
+ vm = vmware_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
+ yield from vm.reload()
+ response.set_status(204)
diff --git a/gns3server/modules/__init__.py b/gns3server/modules/__init__.py
index 928bb1a9..7c23503c 100644
--- a/gns3server/modules/__init__.py
+++ b/gns3server/modules/__init__.py
@@ -21,8 +21,9 @@ from .vpcs import VPCS
from .virtualbox import VirtualBox
from .dynamips import Dynamips
from .qemu import Qemu
+from .vmware import VMware
-MODULES = [VPCS, VirtualBox, Dynamips, Qemu]
+MODULES = [VPCS, VirtualBox, Dynamips, Qemu, VMware]
if sys.platform.startswith("linux") or hasattr(sys, "_called_from_test") or os.environ.get("PYTEST_BUILD_DOCUMENTATION") == "1":
diff --git a/gns3server/modules/vmware/__init__.py b/gns3server/modules/vmware/__init__.py
new file mode 100644
index 00000000..6d876430
--- /dev/null
+++ b/gns3server/modules/vmware/__init__.py
@@ -0,0 +1,106 @@
+# -*- 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 .
+
+"""
+VMware player/workstation server module.
+"""
+
+import os
+import sys
+import shutil
+import asyncio
+import subprocess
+import logging
+
+log = logging.getLogger(__name__)
+
+from ..base_manager import BaseManager
+from .vmware_vm import VMwareVM
+from .vmware_error import VMwareError
+
+
+class VMware(BaseManager):
+
+ _VM_CLASS = VMwareVM
+
+ def __init__(self):
+
+ super().__init__()
+ self._vmrun_path = None
+ self._host_type = "player"
+
+ @property
+ def vmrun_path(self):
+ """
+ Returns the path vmrun utility.
+
+ :returns: path
+ """
+
+ return self._vmrun_path
+
+ def find_vmrun(self):
+
+ # look for vmrun
+ vmrun_path = self.config.get_section_config("VMware").get("vmrun_path")
+ if not vmrun_path:
+ if sys.platform.startswith("win"):
+ pass # TODO: use registry to find vmrun
+ elif sys.platform.startswith("darwin"):
+ vmrun_path = "/Applications/VMware Fusion.app/Contents/Library/vmrun"
+ else:
+ vmrun_path = shutil.which("vmrun")
+
+ if not vmrun_path:
+ raise VMwareError("Could not find vmrun")
+ if not os.path.isfile(vmrun_path):
+ raise VMwareError("vmrun {} is not accessible".format(vmrun_path))
+ if not os.access(vmrun_path, os.X_OK):
+ raise VMwareError("vmrun is not executable")
+ if os.path.basename(vmrun_path) not in ["vmrun", "vmrun.exe"]:
+ raise VMwareError("Invalid vmrun executable name {}".format(os.path.basename(vmrun_path)))
+
+ self._vmrun_path = vmrun_path
+ return vmrun_path
+
+ @asyncio.coroutine
+ def execute(self, subcommand, args, timeout=60, host_type=None):
+
+ vmrun_path = self.vmrun_path
+ if not vmrun_path:
+ vmrun_path = self.find_vmrun()
+ if host_type is None:
+ host_type = self._host_type
+ command = [vmrun_path, "-T", host_type, subcommand]
+ command.extend(args)
+ log.debug("Executing vmrun with command: {}".format(command))
+ try:
+ process = yield from asyncio.create_subprocess_exec(*command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
+ except (OSError, subprocess.SubprocessError) as e:
+ raise VMwareError("Could not execute vmrun: {}".format(e))
+
+ try:
+ stdout_data, _ = yield from asyncio.wait_for(process.communicate(), timeout=timeout)
+ except asyncio.TimeoutError:
+ raise VMwareError("vmrun has timed out after {} seconds!".format(timeout))
+
+ if process.returncode:
+ # vmrun print errors on stdout
+ vmrun_error = stdout_data.decode("utf-8", errors="ignore")
+ raise VMwareError("vmrun has returned an error: {}".format(vmrun_error))
+
+ return stdout_data.decode("utf-8", errors="ignore").splitlines()
diff --git a/gns3server/modules/vmware/vmware_error.py b/gns3server/modules/vmware/vmware_error.py
new file mode 100644
index 00000000..8a254030
--- /dev/null
+++ b/gns3server/modules/vmware/vmware_error.py
@@ -0,0 +1,27 @@
+# -*- 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 .
+
+"""
+Custom exceptions for the VirtualBox module.
+"""
+
+from ..vm_error import VMError
+
+
+class VMwareError(VMError):
+
+ pass
diff --git a/gns3server/modules/vmware/vmware_vm.py b/gns3server/modules/vmware/vmware_vm.py
new file mode 100644
index 00000000..1bc1feee
--- /dev/null
+++ b/gns3server/modules/vmware/vmware_vm.py
@@ -0,0 +1,192 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2014 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 .
+
+"""
+VMware VM instance.
+"""
+
+import sys
+import shlex
+import re
+import os
+import tempfile
+import json
+import socket
+import asyncio
+
+from pkg_resources import parse_version
+from .vmware_error import VMwareError
+from ..nios.nio_udp import NIOUDP
+from ..base_vm import BaseVM
+
+import logging
+log = logging.getLogger(__name__)
+
+
+class VMwareVM(BaseVM):
+
+ """
+ VMware VM implementation.
+ """
+
+ def __init__(self, name, vm_id, project, manager, vmx_path, linked_clone, console=None):
+
+ super().__init__(name, vm_id, project, manager, console=console)
+
+ self._linked_clone = linked_clone
+ self._closed = False
+
+ # VMware VM settings
+ self._headless = False
+ self._vmx_path = vmx_path
+
+ def __json__(self):
+
+ return {"name": self.name,
+ "vm_id": self.id,
+ "console": self.console,
+ "project_id": self.project.id,
+ "vmx_path": self.vmx_path,
+ "headless": self.headless}
+
+ @asyncio.coroutine
+ def _control_vm(self, subcommand, *additional_args):
+
+ args = [self._vmx_path]
+ args.extend(additional_args)
+ result = yield from self.manager.execute(subcommand, args)
+ log.debug("Control VM '{}' result: {}".format(subcommand, result))
+ return result
+
+ @asyncio.coroutine
+ def start(self):
+ """
+ Starts this VMware VM.
+ """
+
+ if self._headless:
+ yield from self._control_vm("start", "nogui")
+ else:
+ yield from self._control_vm("start")
+ log.info("VMware VM '{name}' [{id}] started".format(name=self.name, id=self.id))
+
+ @asyncio.coroutine
+ def stop(self):
+ """
+ Stops this VMware VM.
+ """
+
+ yield from self._control_vm("stop")
+ log.info("VMware VM '{name}' [{id}] stopped".format(name=self.name, id=self.id))
+
+ @asyncio.coroutine
+ def suspend(self):
+ """
+ Suspends this VMware VM.
+ """
+
+ yield from self._control_vm("suspend")
+ log.info("VMware VM '{name}' [{id}] stopped".format(name=self.name, id=self.id))
+
+ @asyncio.coroutine
+ def resume(self):
+ """
+ Resumes this VMware VM.
+ """
+
+ yield from self.start()
+ log.info("VMware VM '{name}' [{id}] resumed".format(name=self.name, id=self.id))
+
+ @asyncio.coroutine
+ def reload(self):
+ """
+ Reloads this VMware VM.
+ """
+
+ yield from self._control_vm("reset")
+ log.info("VMware VM '{name}' [{id}] reloaded".format(name=self.name, id=self.id))
+
+ @asyncio.coroutine
+ def close(self):
+ """
+ Closes this VirtualBox VM.
+ """
+
+ if self._closed:
+ # VM is already closed
+ return
+
+ log.debug("VMware VM '{name}' [{id}] is closing".format(name=self.name, id=self.id))
+ if self._console:
+ self._manager.port_manager.release_tcp_port(self._console, self._project)
+ self._console = None
+
+ #for adapter in self._ethernet_adapters.values():
+ # if adapter is not None:
+ # for nio in adapter.ports.values():
+ # if nio and isinstance(nio, NIOUDP):
+ # self.manager.port_manager.release_udp_port(nio.lport, self._project)
+
+ yield from self.stop()
+
+ log.info("VirtualBox VM '{name}' [{id}] closed".format(name=self.name, id=self.id))
+ self._closed = True
+
+ @property
+ def headless(self):
+ """
+ Returns either the VM will start in headless mode
+
+ :returns: boolean
+ """
+
+ return self._headless
+
+ @headless.setter
+ def headless(self, headless):
+ """
+ Sets either the VM will start in headless mode
+
+ :param headless: boolean
+ """
+
+ if headless:
+ log.info("VMware VM '{name}' [{id}] has enabled the headless mode".format(name=self.name, id=self.id))
+ else:
+ log.info("VMware VM '{name}' [{id}] has disabled the headless mode".format(name=self.name, id=self.id))
+ self._headless = headless
+
+ @property
+ def vmx_path(self):
+ """
+ Returns the path to the vmx file.
+
+ :returns: VMware vmx file
+ """
+
+ return self._vmx_path
+
+ @vmx_path.setter
+ def vmx_path(self, vmx_path):
+ """
+ Sets the path to the vmx file.
+
+ :param vmx_path: VMware vmx file
+ """
+
+ log.info("VMware VM '{name}' [{id}] has set the vmx file path to '{vmx}'".format(name=self.name, id=self.id, vmx=vmx_path))
+ self._vmx_path = vmx_path
diff --git a/gns3server/schemas/vmware.py b/gns3server/schemas/vmware.py
new file mode 100644
index 00000000..959bef5b
--- /dev/null
+++ b/gns3server/schemas/vmware.py
@@ -0,0 +1,143 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2014 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 .
+
+
+VMWARE_CREATE_SCHEMA = {
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "description": "Request validation to create a new VMware VM instance",
+ "type": "object",
+ "properties": {
+ "vm_id": {
+ "description": "VMware VM instance identifier",
+ "type": "string",
+ "minLength": 36,
+ "maxLength": 36,
+ "pattern": "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$"
+ },
+ "linked_clone": {
+ "description": "either the VM is a linked clone or not",
+ "type": "boolean"
+ },
+ "name": {
+ "description": "VMware VM instance name",
+ "type": "string",
+ "minLength": 1,
+ },
+ "vmx_path": {
+ "description": "path to the vmx file",
+ "type": "string",
+ "minLength": 1,
+ },
+ "console": {
+ "description": "console TCP port",
+ "minimum": 1,
+ "maximum": 65535,
+ "type": "integer"
+ },
+ "enable_remote_console": {
+ "description": "enable the remote console",
+ "type": "boolean"
+ },
+ "headless": {
+ "description": "headless mode",
+ "type": "boolean"
+ },
+ },
+ "additionalProperties": False,
+ "required": ["name", "vmx_path", "linked_clone"],
+}
+
+VMWARE_UPDATE_SCHEMA = {
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "description": "Request validation to update a VMware VM instance",
+ "type": "object",
+ "properties": {
+ "name": {
+ "description": "VMware VM instance name",
+ "type": "string",
+ "minLength": 1,
+ },
+ "vmx_path": {
+ "description": "path to the vmx file",
+ "type": "string",
+ "minLength": 1,
+ },
+ "console": {
+ "description": "console TCP port",
+ "minimum": 1,
+ "maximum": 65535,
+ "type": "integer"
+ },
+ "enable_remote_console": {
+ "description": "enable the remote console",
+ "type": "boolean"
+ },
+ "headless": {
+ "description": "headless mode",
+ "type": "boolean"
+ },
+ },
+ "additionalProperties": False,
+}
+
+VMWARE_OBJECT_SCHEMA = {
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "description": "VMware VM instance",
+ "type": "object",
+ "properties": {
+ "name": {
+ "description": "VMware VM instance name",
+ "type": "string",
+ "minLength": 1,
+ },
+ "vm_id": {
+ "description": "VMware VM instance UUID",
+ "type": "string",
+ "minLength": 36,
+ "maxLength": 36,
+ "pattern": "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$"
+ },
+ "project_id": {
+ "description": "Project UUID",
+ "type": "string",
+ "minLength": 36,
+ "maxLength": 36,
+ "pattern": "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$"
+ },
+ "vmx_path": {
+ "description": "path to the vmx file",
+ "type": "string",
+ "minLength": 1,
+ },
+ "enable_remote_console": {
+ "description": "enable the remote console",
+ "type": "boolean"
+ },
+ "headless": {
+ "description": "headless mode",
+ "type": "boolean"
+ },
+ "console": {
+ "description": "console TCP port",
+ "minimum": 1,
+ "maximum": 65535,
+ "type": "integer"
+ },
+ },
+ "additionalProperties": False,
+ "required": ["name", "vm_id", "project_id"]
+}