VPCS Device => VPCS VM

pull/100/head
Julien Duponchelle 9 years ago
parent 68d0e5f42d
commit 7f185663d1

@ -1,8 +1,8 @@
curl -i -X POST 'http://localhost:8000/project' -d '{"location": "/private/var/folders/3s/r2wbv07n7wg4vrsn874lmxxh0000gn/T/pytest-301/test_create_project_with_dir0"}'
curl -i -X POST 'http://localhost:8000/project' -d '{"location": "/private/var/folders/3s/r2wbv07n7wg4vrsn874lmxxh0000gn/T/pytest-308/test_create_project_with_dir0"}'
POST /project HTTP/1.1
{
"location": "/private/var/folders/3s/r2wbv07n7wg4vrsn874lmxxh0000gn/T/pytest-301/test_create_project_with_dir0"
"location": "/private/var/folders/3s/r2wbv07n7wg4vrsn874lmxxh0000gn/T/pytest-308/test_create_project_with_dir0"
}
@ -15,6 +15,6 @@ SERVER: Python/3.4 aiohttp/0.13.1
X-ROUTE: /project
{
"location": "/private/var/folders/3s/r2wbv07n7wg4vrsn874lmxxh0000gn/T/pytest-301/test_create_project_with_dir0",
"uuid": "d4d66c3f-439b-4ae9-972b-59040189c995"
"location": "/private/var/folders/3s/r2wbv07n7wg4vrsn874lmxxh0000gn/T/pytest-308/test_create_project_with_dir0",
"uuid": "7b9efb50-4909-4dc2-bb61-0bf443874c4c"
}

@ -1,652 +0,0 @@
# -*- 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 <http://www.gnu.org/licenses/>.
"""
VPCS server module.
"""
import os
import base64
import socket
import shutil
from gns3server.modules import IModule
from gns3server.config import Config
from .vpcs_device import VPCSDevice
from .vpcs_error import VPCSError
from .nios.nio_udp import NIO_UDP
from .nios.nio_tap import NIO_TAP
from ..attic import find_unused_port
from .schemas import VPCS_CREATE_SCHEMA
from .schemas import VPCS_DELETE_SCHEMA
from .schemas import VPCS_UPDATE_SCHEMA
from .schemas import VPCS_START_SCHEMA
from .schemas import VPCS_STOP_SCHEMA
from .schemas import VPCS_RELOAD_SCHEMA
from .schemas import VPCS_ALLOCATE_UDP_PORT_SCHEMA
from .schemas import VPCS_ADD_NIO_SCHEMA
from .schemas import VPCS_DELETE_NIO_SCHEMA
from .schemas import VPCS_EXPORT_CONFIG_SCHEMA
import logging
log = logging.getLogger(__name__)
class VPCS(IModule):
"""
VPCS module.
:param name: module name
:param args: arguments for the module
:param kwargs: named arguments for the module
"""
def __init__(self, name, *args, **kwargs):
# get the VPCS location
config = Config.instance()
vpcs_config = config.get_section_config(name.upper())
self._vpcs = vpcs_config.get("vpcs_path")
if not self._vpcs or not os.path.isfile(self._vpcs):
paths = [os.getcwd()] + os.environ["PATH"].split(os.pathsep)
# look for VPCS in the current working directory and $PATH
for path in paths:
try:
if "vpcs" in os.listdir(path) and os.access(os.path.join(path, "vpcs"), os.X_OK):
self._vpcs = os.path.join(path, "vpcs")
break
except OSError:
continue
if not self._vpcs:
log.warning("VPCS binary couldn't be found!")
elif not os.access(self._vpcs, os.X_OK):
log.warning("VPCS is not executable")
# a new process start when calling IModule
IModule.__init__(self, name, *args, **kwargs)
self._vpcs_instances = {}
self._console_start_port_range = vpcs_config.get("console_start_port_range", 4501)
self._console_end_port_range = vpcs_config.get("console_end_port_range", 5000)
self._allocated_udp_ports = []
self._udp_start_port_range = vpcs_config.get("udp_start_port_range", 20501)
self._udp_end_port_range = vpcs_config.get("udp_end_port_range", 21000)
self._host = vpcs_config.get("host", kwargs["host"])
self._console_host = vpcs_config.get("console_host", kwargs["console_host"])
self._projects_dir = kwargs["projects_dir"]
self._tempdir = kwargs["temp_dir"]
self._working_dir = self._projects_dir
def stop(self, signum=None):
"""
Properly stops the module.
:param signum: signal number (if called by the signal handler)
"""
# delete all VPCS instances
for vpcs_id in self._vpcs_instances:
vpcs_instance = self._vpcs_instances[vpcs_id]
vpcs_instance.delete()
IModule.stop(self, signum) # this will stop the I/O loop
def get_vpcs_instance(self, vpcs_id):
"""
Returns a VPCS device instance.
:param vpcs_id: VPCS device identifier
:returns: VPCSDevice instance
"""
if vpcs_id not in self._vpcs_instances:
log.debug("VPCS device ID {} doesn't exist".format(vpcs_id), exc_info=1)
self.send_custom_error("VPCS device ID {} doesn't exist".format(vpcs_id))
return None
return self._vpcs_instances[vpcs_id]
@IModule.route("vpcs.reset")
def reset(self, request):
"""
Resets the module.
:param request: JSON request
"""
# delete all vpcs instances
for vpcs_id in self._vpcs_instances:
vpcs_instance = self._vpcs_instances[vpcs_id]
vpcs_instance.delete()
# resets the instance IDs
VPCSDevice.reset()
self._vpcs_instances.clear()
self._allocated_udp_ports.clear()
self._working_dir = self._projects_dir
log.info("VPCS module has been reset")
@IModule.route("vpcs.settings")
def settings(self, request):
"""
Set or update settings.
Optional request parameters:
- path (path to vpcs)
- working_dir (path to a working directory)
- project_name
- console_start_port_range
- console_end_port_range
- udp_start_port_range
- udp_end_port_range
:param request: JSON request
"""
if request is None:
self.send_param_error()
return
if "path" in request and request["path"]:
self._vpcs = request["path"]
log.info("VPCS path set to {}".format(self._vpcs))
for vpcs_id in self._vpcs_instances:
vpcs_instance = self._vpcs_instances[vpcs_id]
vpcs_instance.path = self._vpcs
if "working_dir" in request:
new_working_dir = request["working_dir"]
log.info("this server is local with working directory path to {}".format(new_working_dir))
else:
new_working_dir = os.path.join(self._projects_dir, request["project_name"])
log.info("this server is remote with working directory path to {}".format(new_working_dir))
if self._projects_dir != self._working_dir != new_working_dir:
if not os.path.isdir(new_working_dir):
try:
shutil.move(self._working_dir, new_working_dir)
except OSError as e:
log.error("could not move working directory from {} to {}: {}".format(self._working_dir,
new_working_dir,
e))
return
# update the working directory if it has changed
if self._working_dir != new_working_dir:
self._working_dir = new_working_dir
for vpcs_id in self._vpcs_instances:
vpcs_instance = self._vpcs_instances[vpcs_id]
vpcs_instance.working_dir = os.path.join(self._working_dir, "vpcs", "pc-{}".format(vpcs_instance.id))
if "console_start_port_range" in request and "console_end_port_range" in request:
self._console_start_port_range = request["console_start_port_range"]
self._console_end_port_range = request["console_end_port_range"]
if "udp_start_port_range" in request and "udp_end_port_range" in request:
self._udp_start_port_range = request["udp_start_port_range"]
self._udp_end_port_range = request["udp_end_port_range"]
log.debug("received request {}".format(request))
@IModule.route("vpcs.create")
def vpcs_create(self, request):
"""
Creates a new VPCS instance.
Mandatory request parameters:
- name (VPCS name)
Optional request parameters:
- console (VPCS console port)
Response parameters:
- id (VPCS instance identifier)
- name (VPCS name)
- default settings
:param request: JSON request
"""
# validate the request
if not self.validate_request(request, VPCS_CREATE_SCHEMA):
return
name = request["name"]
console = request.get("console")
vpcs_id = request.get("vpcs_id")
try:
if not self._vpcs:
raise VPCSError("No path to a VPCS executable has been set")
vpcs_instance = VPCSDevice(name,
self._vpcs,
self._working_dir,
vpcs_id,
console,
self._console_host,
self._console_start_port_range,
self._console_end_port_range)
except VPCSError as e:
self.send_custom_error(str(e))
return
response = {"name": vpcs_instance.name,
"id": vpcs_instance.id}
defaults = vpcs_instance.defaults()
response.update(defaults)
self._vpcs_instances[vpcs_instance.id] = vpcs_instance
self.send_response(response)
@IModule.route("vpcs.delete")
def vpcs_delete(self, request):
"""
Deletes a VPCS instance.
Mandatory request parameters:
- id (VPCS instance identifier)
Response parameter:
- True on success
:param request: JSON request
"""
# validate the request
if not self.validate_request(request, VPCS_DELETE_SCHEMA):
return
# get the instance
vpcs_instance = self.get_vpcs_instance(request["id"])
if not vpcs_instance:
return
try:
vpcs_instance.clean_delete()
del self._vpcs_instances[request["id"]]
except VPCSError as e:
self.send_custom_error(str(e))
return
self.send_response(True)
@IModule.route("vpcs.update")
def vpcs_update(self, request):
"""
Updates a VPCS instance
Mandatory request parameters:
- id (VPCS instance identifier)
Optional request parameters:
- any setting to update
- script_file_base64 (base64 encoded)
Response parameters:
- updated settings
:param request: JSON request
"""
# validate the request
if not self.validate_request(request, VPCS_UPDATE_SCHEMA):
return
# get the instance
vpcs_instance = self.get_vpcs_instance(request["id"])
if not vpcs_instance:
return
config_path = os.path.join(vpcs_instance.working_dir, "startup.vpc")
try:
if "script_file_base64" in request:
# a new startup-config has been pushed
config = base64.decodebytes(request["script_file_base64"].encode("utf-8")).decode("utf-8")
config = config.replace("\r", "")
config = config.replace('%h', vpcs_instance.name)
try:
with open(config_path, "w") as f:
log.info("saving script file to {}".format(config_path))
f.write(config)
except OSError as e:
raise VPCSError("Could not save the configuration {}: {}".format(config_path, e))
# update the request with the new local startup-config path
request["script_file"] = os.path.basename(config_path)
elif "script_file" in request:
if os.path.isfile(request["script_file"]) and request["script_file"] != config_path:
# this is a local file set in the GUI
try:
with open(request["script_file"], "r", errors="replace") as f:
config = f.read()
with open(config_path, "w") as f:
config = config.replace("\r", "")
config = config.replace('%h', vpcs_instance.name)
f.write(config)
request["script_file"] = os.path.basename(config_path)
except OSError as e:
raise VPCSError("Could not save the configuration from {} to {}: {}".format(request["script_file"], config_path, e))
elif not os.path.isfile(config_path):
raise VPCSError("Startup-config {} could not be found on this server".format(request["script_file"]))
except VPCSError as e:
self.send_custom_error(str(e))
return
# update the VPCS settings
response = {}
for name, value in request.items():
if hasattr(vpcs_instance, name) and getattr(vpcs_instance, name) != value:
try:
setattr(vpcs_instance, name, value)
response[name] = value
except VPCSError as e:
self.send_custom_error(str(e))
return
self.send_response(response)
@IModule.route("vpcs.start")
def vpcs_start(self, request):
"""
Starts a VPCS instance.
Mandatory request parameters:
- id (VPCS instance identifier)
Response parameters:
- True on success
:param request: JSON request
"""
# validate the request
if not self.validate_request(request, VPCS_START_SCHEMA):
return
# get the instance
vpcs_instance = self.get_vpcs_instance(request["id"])
if not vpcs_instance:
return
try:
vpcs_instance.start()
except VPCSError as e:
self.send_custom_error(str(e))
return
self.send_response(True)
@IModule.route("vpcs.stop")
def vpcs_stop(self, request):
"""
Stops a VPCS instance.
Mandatory request parameters:
- id (VPCS instance identifier)
Response parameters:
- True on success
:param request: JSON request
"""
# validate the request
if not self.validate_request(request, VPCS_STOP_SCHEMA):
return
# get the instance
vpcs_instance = self.get_vpcs_instance(request["id"])
if not vpcs_instance:
return
try:
vpcs_instance.stop()
except VPCSError as e:
self.send_custom_error(str(e))
return
self.send_response(True)
@IModule.route("vpcs.reload")
def vpcs_reload(self, request):
"""
Reloads a VPCS instance.
Mandatory request parameters:
- id (VPCS identifier)
Response parameters:
- True on success
:param request: JSON request
"""
# validate the request
if not self.validate_request(request, VPCS_RELOAD_SCHEMA):
return
# get the instance
vpcs_instance = self.get_vpcs_instance(request["id"])
if not vpcs_instance:
return
try:
if vpcs_instance.is_running():
vpcs_instance.stop()
vpcs_instance.start()
except VPCSError as e:
self.send_custom_error(str(e))
return
self.send_response(True)
@IModule.route("vpcs.allocate_udp_port")
def allocate_udp_port(self, request):
"""
Allocates a UDP port in order to create an UDP NIO.
Mandatory request parameters:
- id (VPCS identifier)
- port_id (unique port identifier)
Response parameters:
- port_id (unique port identifier)
- lport (allocated local port)
:param request: JSON request
"""
# validate the request
if not self.validate_request(request, VPCS_ALLOCATE_UDP_PORT_SCHEMA):
return
# get the instance
vpcs_instance = self.get_vpcs_instance(request["id"])
if not vpcs_instance:
return
try:
port = find_unused_port(self._udp_start_port_range,
self._udp_end_port_range,
host=self._host,
socket_type="UDP",
ignore_ports=self._allocated_udp_ports)
except Exception as e:
self.send_custom_error(str(e))
return
self._allocated_udp_ports.append(port)
log.info("{} [id={}] has allocated UDP port {} with host {}".format(vpcs_instance.name,
vpcs_instance.id,
port,
self._host))
response = {"lport": port,
"port_id": request["port_id"]}
self.send_response(response)
@IModule.route("vpcs.add_nio")
def add_nio(self, request):
"""
Adds an NIO (Network Input/Output) for a VPCS instance.
Mandatory request parameters:
- id (VPCS instance identifier)
- port (port number)
- port_id (unique port identifier)
- nio (one of the following)
- type "nio_udp"
- lport (local port)
- rhost (remote host)
- rport (remote port)
- type "nio_tap"
- tap_device (TAP device name e.g. tap0)
Response parameters:
- port_id (unique port identifier)
:param request: JSON request
"""
# validate the request
if not self.validate_request(request, VPCS_ADD_NIO_SCHEMA):
return
# get the instance
vpcs_instance = self.get_vpcs_instance(request["id"])
if not vpcs_instance:
return
port = request["port"]
try:
nio = None
if request["nio"]["type"] == "nio_udp":
lport = request["nio"]["lport"]
rhost = request["nio"]["rhost"]
rport = request["nio"]["rport"]
try:
#TODO: handle IPv6
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock:
sock.connect((rhost, rport))
except OSError as e:
raise VPCSError("Could not create an UDP connection to {}:{}: {}".format(rhost, rport, e))
nio = NIO_UDP(lport, rhost, rport)
elif request["nio"]["type"] == "nio_tap":
tap_device = request["nio"]["tap_device"]
if not self.has_privileged_access(self._vpcs):
raise VPCSError("{} has no privileged access to {}.".format(self._vpcs, tap_device))
nio = NIO_TAP(tap_device)
if not nio:
raise VPCSError("Requested NIO does not exist or is not supported: {}".format(request["nio"]["type"]))
except VPCSError as e:
self.send_custom_error(str(e))
return
try:
vpcs_instance.port_add_nio_binding(port, nio)
except VPCSError as e:
self.send_custom_error(str(e))
return
self.send_response({"port_id": request["port_id"]})
@IModule.route("vpcs.delete_nio")
def delete_nio(self, request):
"""
Deletes an NIO (Network Input/Output).
Mandatory request parameters:
- id (VPCS instance identifier)
- port (port identifier)
Response parameters:
- True on success
:param request: JSON request
"""
# validate the request
if not self.validate_request(request, VPCS_DELETE_NIO_SCHEMA):
return
# get the instance
vpcs_instance = self.get_vpcs_instance(request["id"])
if not vpcs_instance:
return
port = request["port"]
try:
nio = vpcs_instance.port_remove_nio_binding(port)
if isinstance(nio, NIO_UDP) and nio.lport in self._allocated_udp_ports:
self._allocated_udp_ports.remove(nio.lport)
except VPCSError as e:
self.send_custom_error(str(e))
return
self.send_response(True)
@IModule.route("vpcs.export_config")
def export_config(self, request):
"""
Exports the script file from a VPCS instance.
Mandatory request parameters:
- id (vm identifier)
Response parameters:
- script_file_base64 (script file base64 encoded)
- False if no configuration can be exported
"""
# validate the request
if not self.validate_request(request, VPCS_EXPORT_CONFIG_SCHEMA):
return
# get the instance
vpcs_instance = self.get_vpcs_instance(request["id"])
if not vpcs_instance:
return
response = {}
script_file_path = os.path.join(vpcs_instance.working_dir, vpcs_instance.script_file)
try:
with open(script_file_path, "rb") as f:
config = f.read()
response["script_file_base64"] = base64.encodebytes(config).decode("utf-8")
except OSError as e:
self.send_custom_error("unable to export the script file: {}".format(e))
return
if not response:
self.send_response(False)
else:
self.send_response(response)
@IModule.route("vpcs.echo")
def echo(self, request):
"""
Echo end point for testing purposes.
:param request: JSON request
"""
if request is None:
self.send_param_error()
else:
log.debug("received request {}".format(request))
self.send_response(request)

@ -1,104 +0,0 @@
# -*- 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 <http://www.gnu.org/licenses/>.
class Adapter(object):
"""
Base class for adapters.
:param interfaces: number of interfaces supported by this adapter.
"""
def __init__(self, interfaces=1):
self._interfaces = interfaces
self._ports = {}
for port_id in range(0, interfaces):
self._ports[port_id] = None
def removable(self):
"""
Returns True if the adapter can be removed from a slot
and False if not.
:returns: boolean
"""
return True
def port_exists(self, port_id):
"""
Checks if a port exists on this adapter.
:returns: True is the port exists,
False otherwise.
"""
if port_id in self._ports:
return True
return False
def add_nio(self, port_id, nio):
"""
Adds a NIO to a port on this adapter.
:param port_id: port ID (integer)
:param nio: NIO instance
"""
self._ports[port_id] = nio
def remove_nio(self, port_id):
"""
Removes a NIO from a port on this adapter.
:param port_id: port ID (integer)
"""
self._ports[port_id] = None
def get_nio(self, port_id):
"""
Returns the NIO assigned to a port.
:params port_id: port ID (integer)
:returns: NIO instance
"""
return self._ports[port_id]
@property
def ports(self):
"""
Returns port to NIO mapping
:returns: dictionary port -> NIO
"""
return self._ports
@property
def interfaces(self):
"""
Returns the number of interfaces supported by this adapter.
:returns: number of interfaces
"""
return self._interfaces

@ -1,31 +0,0 @@
# -*- 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 <http://www.gnu.org/licenses/>.
from .adapter import Adapter
class EthernetAdapter(Adapter):
"""
VPCS Ethernet adapter.
"""
def __init__(self):
Adapter.__init__(self, interfaces=1)
def __str__(self):
return "VPCS Ethernet adapter"

@ -1,46 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2013 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/>.
"""
Interface for TAP NIOs (UNIX based OSes only).
"""
class NIO_TAP(object):
"""
TAP NIO.
:param tap_device: TAP device name (e.g. tap0)
"""
def __init__(self, tap_device):
self._tap_device = tap_device
@property
def tap_device(self):
"""
Returns the TAP device used by this NIO.
:returns: the TAP device name
"""
return self._tap_device
def __str__(self):
return "NIO TAP"

@ -1,72 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2013 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/>.
"""
Interface for UDP NIOs.
"""
class NIO_UDP(object):
"""
UDP NIO.
:param lport: local port number
:param rhost: remote address/host
:param rport: remote port number
"""
_instance_count = 0
def __init__(self, lport, rhost, rport):
self._lport = lport
self._rhost = rhost
self._rport = rport
@property
def lport(self):
"""
Returns the local port
:returns: local port number
"""
return self._lport
@property
def rhost(self):
"""
Returns the remote host
:returns: remote address/host
"""
return self._rhost
@property
def rport(self):
"""
Returns the remote port
:returns: remote port number
"""
return self._rport
def __str__(self):
return "NIO UDP"

@ -1,347 +0,0 @@
# -*- 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 <http://www.gnu.org/licenses/>.
VPCS_CREATE_SCHEMA = {
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Request validation to create a new VPCS instance",
"type": "object",
"properties": {
"name": {
"description": "VPCS device name",
"type": "string",
"minLength": 1,
},
"vpcs_id": {
"description": "VPCS device instance ID",
"type": "integer"
},
"console": {
"description": "console TCP port",
"minimum": 1,
"maximum": 65535,
"type": "integer"
},
},
"additionalProperties": False,
"required": ["name"]
}
VPCS_DELETE_SCHEMA = {
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Request validation to delete a VPCS instance",
"type": "object",
"properties": {
"id": {
"description": "VPCS device instance ID",
"type": "integer"
},
},
"additionalProperties": False,
"required": ["id"]
}
VPCS_UPDATE_SCHEMA = {
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Request validation to update a VPCS instance",
"type": "object",
"properties": {
"id": {
"description": "VPCS device instance ID",
"type": "integer"
},
"name": {
"description": "VPCS device name",
"type": "string",
"minLength": 1,
},
"console": {
"description": "console TCP port",
"minimum": 1,
"maximum": 65535,
"type": "integer"
},
"script_file": {
"description": "Path to the VPCS script file file",
"type": "string",
"minLength": 1,
},
"script_file_base64": {
"description": "Script file base64 encoded",
"type": "string"
},
},
"additionalProperties": False,
"required": ["id"]
}
VPCS_START_SCHEMA = {
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Request validation to start a VPCS instance",
"type": "object",
"properties": {
"id": {
"description": "VPCS device instance ID",
"type": "integer"
},
},
"additionalProperties": False,
"required": ["id"]
}
VPCS_STOP_SCHEMA = {
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Request validation to stop a VPCS instance",
"type": "object",
"properties": {
"id": {
"description": "VPCS device instance ID",
"type": "integer"
},
},
"additionalProperties": False,
"required": ["id"]
}
VPCS_RELOAD_SCHEMA = {
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Request validation to reload a VPCS instance",
"type": "object",
"properties": {
"id": {
"description": "VPCS device instance ID",
"type": "integer"
},
},
"additionalProperties": False,
"required": ["id"]
}
VPCS_ALLOCATE_UDP_PORT_SCHEMA = {
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Request validation to allocate an UDP port for a VPCS instance",
"type": "object",
"properties": {
"id": {
"description": "VPCS device instance ID",
"type": "integer"
},
"port_id": {
"description": "Unique port identifier for the VPCS instance",
"type": "integer"
},
},
"additionalProperties": False,
"required": ["id", "port_id"]
}
VPCS_ADD_NIO_SCHEMA = {
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Request validation to add a NIO for a VPCS instance",
"type": "object",
"definitions": {
"UDP": {
"description": "UDP Network Input/Output",
"properties": {
"type": {
"enum": ["nio_udp"]
},
"lport": {
"description": "Local port",
"type": "integer",
"minimum": 1,
"maximum": 65535
},
"rhost": {
"description": "Remote host",
"type": "string",
"minLength": 1
},
"rport": {
"description": "Remote port",
"type": "integer",
"minimum": 1,
"maximum": 65535
}
},
"required": ["type", "lport", "rhost", "rport"],
"additionalProperties": False
},
"Ethernet": {
"description": "Generic Ethernet Network Input/Output",
"properties": {
"type": {
"enum": ["nio_generic_ethernet"]
},
"ethernet_device": {
"description": "Ethernet device name e.g. eth0",
"type": "string",
"minLength": 1
},
},
"required": ["type", "ethernet_device"],
"additionalProperties": False
},
"LinuxEthernet": {
"description": "Linux Ethernet Network Input/Output",
"properties": {
"type": {
"enum": ["nio_linux_ethernet"]
},
"ethernet_device": {
"description": "Ethernet device name e.g. eth0",
"type": "string",
"minLength": 1
},
},
"required": ["type", "ethernet_device"],
"additionalProperties": False
},
"TAP": {
"description": "TAP Network Input/Output",
"properties": {
"type": {
"enum": ["nio_tap"]
},
"tap_device": {
"description": "TAP device name e.g. tap0",
"type": "string",
"minLength": 1
},
},
"required": ["type", "tap_device"],
"additionalProperties": False
},
"UNIX": {
"description": "UNIX Network Input/Output",
"properties": {
"type": {
"enum": ["nio_unix"]
},
"local_file": {
"description": "path to the UNIX socket file (local)",
"type": "string",
"minLength": 1
},
"remote_file": {
"description": "path to the UNIX socket file (remote)",
"type": "string",
"minLength": 1
},
},
"required": ["type", "local_file", "remote_file"],
"additionalProperties": False
},
"VDE": {
"description": "VDE Network Input/Output",
"properties": {
"type": {
"enum": ["nio_vde"]
},
"control_file": {
"description": "path to the VDE control file",
"type": "string",
"minLength": 1
},
"local_file": {
"description": "path to the VDE control file",
"type": "string",
"minLength": 1
},
},
"required": ["type", "control_file", "local_file"],
"additionalProperties": False
},
"NULL": {
"description": "NULL Network Input/Output",
"properties": {
"type": {
"enum": ["nio_null"]
},
},
"required": ["type"],
"additionalProperties": False
},
},
"properties": {
"id": {
"description": "VPCS device instance ID",
"type": "integer"
},
"port_id": {
"description": "Unique port identifier for the VPCS instance",
"type": "integer"
},
"port": {
"description": "Port number",
"type": "integer",
"minimum": 0,
"maximum": 0
},
"nio": {
"type": "object",
"description": "Network Input/Output",
"oneOf": [
{"$ref": "#/definitions/UDP"},
{"$ref": "#/definitions/Ethernet"},
{"$ref": "#/definitions/LinuxEthernet"},
{"$ref": "#/definitions/TAP"},
{"$ref": "#/definitions/UNIX"},
{"$ref": "#/definitions/VDE"},
{"$ref": "#/definitions/NULL"},
]
},
},
"additionalProperties": False,
"required": ["id", "port_id", "port", "nio"]
}
VPCS_DELETE_NIO_SCHEMA = {
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Request validation to delete a NIO for a VPCS instance",
"type": "object",
"properties": {
"id": {
"description": "VPCS device instance ID",
"type": "integer"
},
"port": {
"description": "Port number",
"type": "integer",
"minimum": 0,
"maximum": 0
},
},
"additionalProperties": False,
"required": ["id", "port"]
}
VPCS_EXPORT_CONFIG_SCHEMA = {
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Request validation to export the script file of a VPCS instance",
"type": "object",
"properties": {
"id": {
"description": "VPCS device instance ID",
"type": "integer"
},
},
"additionalProperties": False,
"required": ["id"]
}

@ -1,557 +0,0 @@
# -*- 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 <http://www.gnu.org/licenses/>.
"""
VPCS device management (creates command line, processes, files etc.) in
order to run an VPCS instance.
"""
import os
import sys
import subprocess
import signal
import shutil
import re
from pkg_resources import parse_version
from .vpcs_error import VPCSError
from .adapters.ethernet_adapter import EthernetAdapter
from .nios.nio_udp import NIO_UDP
from .nios.nio_tap import NIO_TAP
from ..attic import find_unused_port
import logging
log = logging.getLogger(__name__)
class VPCSDevice(object):
"""
VPCS device implementation.
:param name: name of this VPCS device
:param path: path to VPCS executable
:param working_dir: path to a working directory
:param vpcs_id: VPCS instance ID
:param console: TCP console port
:param console_host: IP address to bind for console connections
:param console_start_port_range: TCP console port range start
:param console_end_port_range: TCP console port range end
"""
_instances = []
_allocated_console_ports = []
def __init__(self,
name,
path,
working_dir,
vpcs_id=None,
console=None,
console_host="0.0.0.0",
console_start_port_range=4512,
console_end_port_range=5000):
if not vpcs_id:
# find an instance identifier is none is provided (1 <= id <= 255)
# This 255 limit is due to a restriction on the number of possible
# MAC addresses given in VPCS using the -m option
self._id = 0
for identifier in range(1, 256):
if identifier not in self._instances:
self._id = identifier
self._instances.append(self._id)
break
if self._id == 0:
raise VPCSError("Maximum number of VPCS instances reached")
else:
if vpcs_id in self._instances:
raise VPCSError("VPCS identifier {} is already used by another VPCS device".format(vpcs_id))
self._id = vpcs_id
self._instances.append(self._id)
self._name = name
self._path = path
self._console = console
self._working_dir = None
self._console_host = console_host
self._command = []
self._process = None
self._vpcs_stdout_file = ""
self._started = False
self._console_start_port_range = console_start_port_range
self._console_end_port_range = console_end_port_range
# VPCS settings
self._script_file = ""
self._ethernet_adapter = EthernetAdapter() # one adapter with 1 Ethernet interface
working_dir_path = os.path.join(working_dir, "vpcs", "pc-{}".format(self._id))
if vpcs_id and not os.path.isdir(working_dir_path):
raise VPCSError("Working directory {} doesn't exist".format(working_dir_path))
# create the device own working directory
self.working_dir = working_dir_path
if not self._console:
# allocate a console port
try:
self._console = find_unused_port(self._console_start_port_range,
self._console_end_port_range,
self._console_host,
ignore_ports=self._allocated_console_ports)
except Exception as e:
raise VPCSError(e)
if self._console in self._allocated_console_ports:
raise VPCSError("Console port {} is already used by another VPCS device".format(console))
self._allocated_console_ports.append(self._console)
log.info("VPCS device {name} [id={id}] has been created".format(name=self._name,
id=self._id))
def defaults(self):
"""
Returns all the default attribute values for VPCS.
:returns: default values (dictionary)
"""
vpcs_defaults = {"name": self._name,
"script_file": self._script_file,
"console": self._console}
return vpcs_defaults
@property
def id(self):
"""
Returns the unique ID for this VPCS device.
:returns: id (integer)
"""
return self._id
@classmethod
def reset(cls):
"""
Resets allocated instance list.
"""
cls._instances.clear()
cls._allocated_console_ports.clear()
@property
def name(self):
"""
Returns the name of this VPCS device.
:returns: name
"""
return self._name
@name.setter
def name(self, new_name):
"""
Sets the name of this VPCS device.
:param new_name: name
"""
if self._script_file:
# update the startup.vpc
config_path = os.path.join(self._working_dir, "startup.vpc")
if os.path.isfile(config_path):
try:
with open(config_path, "r+", errors="replace") as f:
old_config = f.read()
new_config = old_config.replace(self._name, new_name)
f.seek(0)
f.write(new_config)
except OSError as e:
raise VPCSError("Could not amend the configuration {}: {}".format(config_path, e))
log.info("VPCS {name} [id={id}]: renamed to {new_name}".format(name=self._name,
id=self._id,
new_name=new_name))
self._name = new_name
@property
def path(self):
"""
Returns the path to the VPCS executable.
:returns: path to VPCS
"""
return self._path
@path.setter
def path(self, path):
"""
Sets the path to the VPCS executable.
:param path: path to VPCS
"""
self._path = path
log.info("VPCS {name} [id={id}]: path changed to {path}".format(name=self._name,
id=self._id,
path=path))
@property
def working_dir(self):
"""
Returns current working directory
:returns: path to the working directory
"""
return self._working_dir
@working_dir.setter
def working_dir(self, working_dir):
"""
Sets the working directory for VPCS.
:param working_dir: path to the working directory
"""
try:
os.makedirs(working_dir)
except FileExistsError:
pass
except OSError as e:
raise VPCSError("Could not create working directory {}: {}".format(working_dir, e))
self._working_dir = working_dir
log.info("VPCS {name} [id={id}]: working directory changed to {wd}".format(name=self._name,
id=self._id,
wd=self._working_dir))
@property
def console(self):
"""
Returns the TCP console port.
:returns: console port (integer)
"""
return self._console
@console.setter
def console(self, console):
"""
Sets the TCP console port.
:param console: console port (integer)
"""
if console in self._allocated_console_ports:
raise VPCSError("Console port {} is already used by another VPCS device".format(console))
self._allocated_console_ports.remove(self._console)
self._console = console
self._allocated_console_ports.append(self._console)
log.info("VPCS {name} [id={id}]: console port set to {port}".format(name=self._name,
id=self._id,
port=console))
def command(self):
"""
Returns the VPCS command line.
:returns: VPCS command line (string)
"""
return " ".join(self._build_command())
def delete(self):
"""
Deletes this VPCS device.
"""
self.stop()
if self._id in self._instances:
self._instances.remove(self._id)
if self.console and self.console in self._allocated_console_ports:
self._allocated_console_ports.remove(self.console)
log.info("VPCS device {name} [id={id}] has been deleted".format(name=self._name,
id=self._id))
def clean_delete(self):
"""
Deletes this VPCS device & all files (configs, logs etc.)
"""
self.stop()
if self._id in self._instances:
self._instances.remove(self._id)
if self.console:
self._allocated_console_ports.remove(self.console)
try:
shutil.rmtree(self._working_dir)
except OSError as e:
log.error("could not delete VPCS device {name} [id={id}]: {error}".format(name=self._name,
id=self._id,
error=e))
return
log.info("VPCS device {name} [id={id}] has been deleted (including associated files)".format(name=self._name,
id=self._id))
@property
def started(self):
"""
Returns either this VPCS device has been started or not.
:returns: boolean
"""
return self._started
def _check_vpcs_version(self):
"""
Checks if the VPCS executable version is >= 0.5b1.
"""
try:
output = subprocess.check_output([self._path, "-v"], cwd=self._working_dir)
match = re.search("Welcome to Virtual PC Simulator, version ([0-9a-z\.]+)", output.decode("utf-8"))
if match:
version = match.group(1)
if parse_version(version) < parse_version("0.5b1"):
raise VPCSError("VPCS executable version must be >= 0.5b1")
else:
raise VPCSError("Could not determine the VPCS version for {}".format(self._path))
except (OSError, subprocess.SubprocessError) as e:
raise VPCSError("Error while looking for the VPCS version: {}".format(e))
def start(self):
"""
Starts the VPCS process.
"""
if not self.is_running():
if not self._path:
raise VPCSError("No path to a VPCS executable has been set")
if not os.path.isfile(self._path):
raise VPCSError("VPCS program '{}' is not accessible".format(self._path))
if not os.access(self._path, os.X_OK):
raise VPCSError("VPCS program '{}' is not executable".format(self._path))
self._check_vpcs_version()
if not self._ethernet_adapter.get_nio(0):
raise VPCSError("This VPCS instance must be connected in order to start")
self._command = self._build_command()
try:
log.info("starting VPCS: {}".format(self._command))
self._vpcs_stdout_file = os.path.join(self._working_dir, "vpcs.log")
log.info("logging to {}".format(self._vpcs_stdout_file))
flags = 0
if sys.platform.startswith("win32"):
flags = subprocess.CREATE_NEW_PROCESS_GROUP
with open(self._vpcs_stdout_file, "w") as fd:
self._process = subprocess.Popen(self._command,
stdout=fd,
stderr=subprocess.STDOUT,
cwd=self._working_dir,
creationflags=flags)
log.info("VPCS instance {} started PID={}".format(self._id, self._process.pid))
self._started = True
except (OSError, subprocess.SubprocessError) as e:
vpcs_stdout = self.read_vpcs_stdout()
log.error("could not start VPCS {}: {}\n{}".format(self._path, e, vpcs_stdout))
raise VPCSError("could not start VPCS {}: {}\n{}".format(self._path, e, vpcs_stdout))
def stop(self):
"""
Stops the VPCS process.
"""
# stop the VPCS process
if self.is_running():
log.info("stopping VPCS instance {} PID={}".format(self._id, self._process.pid))
if sys.platform.startswith("win32"):
self._process.send_signal(signal.CTRL_BREAK_EVENT)
else:
self._process.terminate()
self._process.wait()
self._process = None
self._started = False
def read_vpcs_stdout(self):
"""
Reads the standard output of the VPCS process.
Only use when the process has been stopped or has crashed.
"""
output = ""
if self._vpcs_stdout_file:
try:
with open(self._vpcs_stdout_file, errors="replace") as file:
output = file.read()
except OSError as e:
log.warn("could not read {}: {}".format(self._vpcs_stdout_file, e))
return output
def is_running(self):
"""
Checks if the VPCS process is running
:returns: True or False
"""
if self._process and self._process.poll() is None:
return True
return False
def port_add_nio_binding(self, port_id, nio):
"""
Adds a port NIO binding.
:param port_id: port ID
:param nio: NIO instance to add to the slot/port
"""
if not self._ethernet_adapter.port_exists(port_id):
raise VPCSError("Port {port_id} doesn't exist in adapter {adapter}".format(adapter=self._ethernet_adapter,
port_id=port_id))
self._ethernet_adapter.add_nio(port_id, nio)
log.info("VPCS {name} [id={id}]: {nio} added to port {port_id}".format(name=self._name,
id=self._id,
nio=nio,
port_id=port_id))
def port_remove_nio_binding(self, port_id):
"""
Removes a port NIO binding.
:param port_id: port ID
:returns: NIO instance
"""
if not self._ethernet_adapter.port_exists(port_id):
raise VPCSError("Port {port_id} doesn't exist in adapter {adapter}".format(adapter=self._ethernet_adapter,
port_id=port_id))
nio = self._ethernet_adapter.get_nio(port_id)
self._ethernet_adapter.remove_nio(port_id)
log.info("VPCS {name} [id={id}]: {nio} removed from port {port_id}".format(name=self._name,
id=self._id,
nio=nio,
port_id=port_id))
return nio
def _build_command(self):
"""
Command to start the VPCS process.
(to be passed to subprocess.Popen())
VPCS command line:
usage: vpcs [options] [scriptfile]
Option:
-h print this help then exit
-v print version information then exit
-i num number of vpc instances to start (default is 9)
-p port run as a daemon listening on the tcp 'port'
-m num start byte of ether address, default from 0
-r file load and execute script file
compatible with older versions, DEPRECATED.
-e tap mode, using /dev/tapx by default (linux only)
-u udp mode, default
udp mode options:
-s port local udp base port, default from 20000
-c port remote udp base port (dynamips udp port), default from 30000
-t ip remote host IP, default 127.0.0.1
tap mode options:
-d device device name, works only when -i is set to 1
hypervisor mode option:
-H port run as the hypervisor listening on the tcp 'port'
If no 'scriptfile' specified, vpcs will read and execute the file named
'startup.vpc' if it exsits in the current directory.
"""
command = [self._path]
command.extend(["-p", str(self._console)]) # listen to console port
nio = self._ethernet_adapter.get_nio(0)
if nio:
if isinstance(nio, NIO_UDP):
# UDP tunnel
command.extend(["-s", str(nio.lport)]) # source UDP port
command.extend(["-c", str(nio.rport)]) # destination UDP port
command.extend(["-t", nio.rhost]) # destination host
elif isinstance(nio, NIO_TAP):
# TAP interface
command.extend(["-e"])
command.extend(["-d", nio.tap_device])
command.extend(["-m", str(self._id)]) # the unique ID is used to set the MAC address offset
command.extend(["-i", "1"]) # option to start only one VPC instance
command.extend(["-F"]) # option to avoid the daemonization of VPCS
if self._script_file:
command.extend([self._script_file])
return command
@property
def script_file(self):
"""
Returns the script-file for this VPCS instance.
:returns: path to script-file
"""
return self._script_file
@script_file.setter
def script_file(self, script_file):
"""
Sets the script-file for this VPCS instance.
:param script_file: path to base-script-file
"""
self._script_file = script_file
log.info("VPCS {name} [id={id}]: script_file set to {config}".format(name=self._name,
id=self._id,
config=self._script_file))

@ -1,39 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2013 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 exceptions for VPCS module.
"""
class VPCSError(Exception):
def __init__(self, message, original_exception=None):
Exception.__init__(self, message)
if isinstance(message, Exception):
message = str(message)
self._message = message
self._original_exception = original_exception
def __repr__(self):
return self._message
def __str__(self):
return self._message

@ -20,8 +20,8 @@ VPCS server module.
"""
from ..base_manager import BaseManager
from .vpcs_device import VPCSDevice
from .vpcs_vm import VPCSVM
class VPCS(BaseManager):
_VM_CLASS = VPCSDevice
_VM_CLASS = VPCSVM

@ -16,7 +16,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
VPCS device management (creates command line, processes, files etc.) in
VPCS vm management (creates command line, processes, files etc.) in
order to run an VPCS instance.
"""
@ -42,11 +42,11 @@ import logging
log = logging.getLogger(__name__)
class VPCSDevice(BaseVM):
class VPCSVM(BaseVM):
"""
VPCS device implementation.
VPCS vm implementation.
:param name: name of this VPCS device
:param name: name of this VPCS vm
:param uuid: VPCS instance UUID
:param project: Project instance
:param manager: parent VM Manager
@ -78,7 +78,7 @@ class VPCSDevice(BaseVM):
# if vpcs_id and not os.path.isdir(working_dir_path):
# raise VPCSError("Working directory {} doesn't exist".format(working_dir_path))
#
# # create the device own working directory
# # create the vm own working directory
# self.working_dir = working_dir_path
#
try:
@ -113,7 +113,7 @@ class VPCSDevice(BaseVM):
@property
def console(self):
"""
Returns the console port of this VPCS device.
Returns the console port of this VPCS vm.
:returns: console port
"""
@ -124,7 +124,7 @@ class VPCSDevice(BaseVM):
@BaseVM.name.setter
def name(self, new_name):
"""
Sets the name of this VPCS device.
Sets the name of this VPCS vm.
:param new_name: name
"""
@ -265,10 +265,10 @@ class VPCSDevice(BaseVM):
raise VPCSError("Could not create an UDP connection to {}:{}: {}".format(rhost, rport, e))
nio = NIO_UDP(lport, rhost, rport)
elif nio_settings["type"] == "nio_tap":
tap_device = nio_settings["tap_device"]
tap_vm = nio_settings["tap_device"]
if not has_privileged_access(self._path):
raise VPCSError("{} has no privileged access to {}.".format(self._path, tap_device))
nio = NIO_TAP(tap_device)
raise VPCSError("{} has no privileged access to {}.".format(self._path, tap_vm))
nio = NIO_TAP(tap_vm)
if not nio:
raise VPCSError("Requested NIO does not exist or is not supported: {}".format(nio_settings["type"]))
@ -326,7 +326,7 @@ class VPCSDevice(BaseVM):
-t ip remote host IP, default 127.0.0.1
tap mode options:
-d device device name, works only when -i is set to 1
-d vm device name, works only when -i is set to 1
hypervisor mode option:
-H port run as the hypervisor listening on the tcp 'port'
@ -352,7 +352,7 @@ class VPCSDevice(BaseVM):
elif isinstance(nio, NIO_TAP):
# TAP interface
command.extend(["-e"])
command.extend(["-d", nio.tap_device])
command.extend(["-d", nio.tap_vm])
# FIXME: find workaround
# command.extend(["-m", str(self._id)]) # the unique ID is used to set the MAC address offset

@ -49,7 +49,7 @@ def test_vpcs_nio_create_udp(server, vm):
assert response.json["type"] == "nio_udp"
@patch("gns3server.modules.vpcs.vpcs_device.has_privileged_access", return_value=True)
@patch("gns3server.modules.vpcs.vpcs_vm.has_privileged_access", return_value=True)
def test_vpcs_nio_create_tap(mock, server, vm):
response = server.post("/vpcs/{}/ports/0/nio".format(vm["uuid"]), {"type": "nio_tap",
"tap_device": "test"})

@ -23,7 +23,7 @@ from tests.utils import asyncio_patch
from tests.api.base import loop, project
from asyncio.subprocess import Process
from unittest.mock import patch, MagicMock
from gns3server.modules.vpcs.vpcs_device import VPCSDevice
from gns3server.modules.vpcs.vpcs_vm import VPCSVM
from gns3server.modules.vpcs.vpcs_error import VPCSError
from gns3server.modules.vpcs import VPCS
from gns3server.modules.port_manager import PortManager
@ -38,7 +38,7 @@ def manager():
@patch("subprocess.check_output", return_value="Welcome to Virtual PC Simulator, version 0.6".encode("utf-8"))
def test_vm(manager):
vm = VPCSDevice("test", "00010203-0405-0607-0809-0a0b0c0d0e0f", project, manager)
vm = VPCSVM("test", "00010203-0405-0607-0809-0a0b0c0d0e0f", project, manager)
assert vm.name == "test"
assert vm.uuid == "00010203-0405-0607-0809-0a0b0c0d0e0f"
@ -46,7 +46,7 @@ def test_vm(manager):
@patch("subprocess.check_output", return_value="Welcome to Virtual PC Simulator, version 0.1".encode("utf-8"))
def test_vm_invalid_vpcs_version(project, manager):
with pytest.raises(VPCSError):
vm = VPCSDevice("test", "00010203-0405-0607-0809-0a0b0c0d0e0f", project, manager)
vm = VPCSVM("test", "00010203-0405-0607-0809-0a0b0c0d0e0f", project, manager)
assert vm.name == "test"
assert vm.uuid == "00010203-0405-0607-0809-0a0b0c0d0e0f"
@ -54,14 +54,14 @@ def test_vm_invalid_vpcs_version(project, manager):
@patch("gns3server.config.Config.get_section_config", return_value={"path": "/bin/test_fake"})
def test_vm_invalid_vpcs_path(project, manager):
with pytest.raises(VPCSError):
vm = VPCSDevice("test", "00010203-0405-0607-0809-0a0b0c0d0e0f", project, manager)
vm = VPCSVM("test", "00010203-0405-0607-0809-0a0b0c0d0e0f", project, manager)
assert vm.name == "test"
assert vm.uuid == "00010203-0405-0607-0809-0a0b0c0d0e0f"
def test_start(project, loop, manager):
with asyncio_patch("asyncio.create_subprocess_exec", return_value=MagicMock()):
vm = VPCSDevice("test", "00010203-0405-0607-0809-0a0b0c0d0e0f", project, manager)
vm = VPCSVM("test", "00010203-0405-0607-0809-0a0b0c0d0e0f", project, manager)
nio = vm.port_add_nio_binding(0, {"type": "nio_udp", "lport": 4242, "rport": 4243, "rhost": "127.0.0.1"})
loop.run_until_complete(asyncio.async(vm.start()))
@ -71,7 +71,7 @@ def test_start(project, loop, manager):
def test_stop(project, loop, manager):
process = MagicMock()
with asyncio_patch("asyncio.create_subprocess_exec", return_value=process):
vm = VPCSDevice("test", "00010203-0405-0607-0809-0a0b0c0d0e0f", project, manager)
vm = VPCSVM("test", "00010203-0405-0607-0809-0a0b0c0d0e0f", project, manager)
nio = vm.port_add_nio_binding(0, {"type": "nio_udp", "lport": 4242, "rport": 4243, "rhost": "127.0.0.1"})
loop.run_until_complete(asyncio.async(vm.start()))
@ -82,28 +82,28 @@ def test_stop(project, loop, manager):
def test_add_nio_binding_udp(manager):
vm = VPCSDevice("test", "00010203-0405-0607-0809-0a0b0c0d0e0f", project, manager)
vm = VPCSVM("test", "00010203-0405-0607-0809-0a0b0c0d0e0f", project, manager)
nio = vm.port_add_nio_binding(0, {"type": "nio_udp", "lport": 4242, "rport": 4243, "rhost": "127.0.0.1"})
assert nio.lport == 4242
def test_add_nio_binding_tap(project, manager):
vm = VPCSDevice("test", 42, project, manager)
with patch("gns3server.modules.vpcs.vpcs_device.has_privileged_access", return_value=True):
vm = VPCSVM("test", 42, project, manager)
with patch("gns3server.modules.vpcs.vpcs_vm.has_privileged_access", return_value=True):
nio = vm.port_add_nio_binding(0, {"type": "nio_tap", "tap_device": "test"})
assert nio.tap_device == "test"
def test_add_nio_binding_tap_no_privileged_access(manager):
vm = VPCSDevice("test", "00010203-0405-0607-0809-0a0b0c0d0e0f", project, manager)
with patch("gns3server.modules.vpcs.vpcs_device.has_privileged_access", return_value=False):
vm = VPCSVM("test", "00010203-0405-0607-0809-0a0b0c0d0e0f", project, manager)
with patch("gns3server.modules.vpcs.vpcs_vm.has_privileged_access", return_value=False):
with pytest.raises(VPCSError):
vm.port_add_nio_binding(0, {"type": "nio_tap", "tap_device": "test"})
assert vm._ethernet_adapter.ports[0] is None
def test_port_remove_nio_binding(manager):
vm = VPCSDevice("test", "00010203-0405-0607-0809-0a0b0c0d0e0f", project, manager)
vm = VPCSVM("test", "00010203-0405-0607-0809-0a0b0c0d0e0f", project, manager)
nio = vm.port_add_nio_binding(0, {"type": "nio_udp", "lport": 4242, "rport": 4243, "rhost": "127.0.0.1"})
vm.port_remove_nio_binding(0)
assert vm._ethernet_adapter.ports[0] is None
Loading…
Cancel
Save