mirror of
https://github.com/GNS3/gns3-server
synced 2024-11-28 03:08:14 +00:00
Handle startup-config push using base64
Dynamips working directory management Random port selection for the ZeroMQ server TCP & UDP port allocation in a range with improvements Update Dynamips to 0.2.11 (for the tests) Focus on Python3 development (stop trying to be compatible with Python 2.x) More error/bug catching
This commit is contained in:
parent
687d5b75ab
commit
89888ae7bf
@ -82,8 +82,8 @@ class ModuleManager(object):
|
||||
log.info("loading {} module".format(module_class[0].lower()))
|
||||
info = Module(name=module_class[0].lower(), cls=module_class[1])
|
||||
self._modules.append(info)
|
||||
except:
|
||||
log.warning("error while analyzing {} package directory".format(name))
|
||||
except Exception as e:
|
||||
log.critical("error while analyzing {} package directory".format(name), exc_info=1)
|
||||
finally:
|
||||
if file:
|
||||
file.close()
|
||||
|
@ -93,9 +93,16 @@ class IModule(multiprocessing.Process):
|
||||
stream.on_recv(callback)
|
||||
return stream
|
||||
|
||||
# def add_periodic_callback(self, callback, time):
|
||||
#
|
||||
# self.test = zmq.eventloop.ioloop.PeriodicCallback(callback, time, self._ioloop).start()
|
||||
def add_periodic_callback(self, callback, time):
|
||||
"""
|
||||
Adds a periodic callback to the ioloop.
|
||||
|
||||
:param callback: callback to be called
|
||||
:param time: frequency when the callback is executed
|
||||
"""
|
||||
|
||||
periodic_callback = zmq.eventloop.ioloop.PeriodicCallback(callback, time, self._ioloop)
|
||||
return periodic_callback
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
|
@ -19,6 +19,8 @@
|
||||
Dynamips server module.
|
||||
"""
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
from gns3server.modules import IModule
|
||||
import gns3server.jsonrpc as jsonrpc
|
||||
|
||||
@ -99,12 +101,16 @@ class Dynamips(IModule):
|
||||
IModule.__init__(self, name=name, args=args, kwargs=kwargs)
|
||||
|
||||
self._hypervisor_manager = None
|
||||
self._remote_server = False
|
||||
self._routers = {}
|
||||
self._ethernet_switches = {}
|
||||
self._frame_relay_switches = {}
|
||||
self._atm_switches = {}
|
||||
self._ethernet_hubs = {}
|
||||
|
||||
#self._callback = self.add_periodic_callback(self.test, 1000)
|
||||
#self._callback.start()
|
||||
|
||||
def stop(self):
|
||||
"""
|
||||
Properly stops the module.
|
||||
@ -148,6 +154,8 @@ class Dynamips(IModule):
|
||||
self._frame_relay_switches.clear()
|
||||
self._atm_switches.clear()
|
||||
|
||||
self._hypervisor_manager = None
|
||||
self._remote_server = False
|
||||
log.info("dynamips module has been reset")
|
||||
|
||||
@IModule.route("dynamips.settings")
|
||||
@ -155,6 +163,12 @@ class Dynamips(IModule):
|
||||
"""
|
||||
Set or update settings.
|
||||
|
||||
Mandatory request parameters:
|
||||
- path (path to the Dynamips executable)
|
||||
|
||||
Optional request parameters:
|
||||
- working_dir (path to a working directory)
|
||||
|
||||
:param request: JSON request
|
||||
"""
|
||||
|
||||
@ -167,9 +181,32 @@ class Dynamips(IModule):
|
||||
#TODO: JSON schema validation
|
||||
# starts the hypervisor manager if it hasn't been started yet
|
||||
if not self._hypervisor_manager:
|
||||
#TODO: working dir support
|
||||
log.info("starting the hypervisor manager with Dynamips working directory set to '{}'".format("/tmp"))
|
||||
self._hypervisor_manager = HypervisorManager(request["path"], "/tmp")
|
||||
dynamips_path = request["path"]
|
||||
|
||||
if "working_dir" in request:
|
||||
working_dir = request["working_dir"]
|
||||
log.info("this server is local")
|
||||
else:
|
||||
self._remote_server = True
|
||||
log.info("this server is remote")
|
||||
try:
|
||||
working_dir = tempfile.mkdtemp(prefix="gns3-remote-server-")
|
||||
working_dir = os.path.join(working_dir, "dynamips")
|
||||
os.makedirs(working_dir)
|
||||
log.info("temporary working directory created: {}".format(working_dir))
|
||||
except EnvironmentError as e:
|
||||
raise DynamipsError("Could not create temporary working directory: {}".format(e))
|
||||
|
||||
#TODO: check if executable
|
||||
if not os.path.exists(dynamips_path):
|
||||
raise DynamipsError("Dynamips executable {} doesn't exist".format(working_dir))
|
||||
|
||||
#TODO: check if writable
|
||||
if not os.path.exists(working_dir):
|
||||
raise DynamipsError("Working directory {} doesn't exist".format(working_dir))
|
||||
|
||||
log.info("starting the hypervisor manager with Dynamips working directory set to '{}'".format(working_dir))
|
||||
self._hypervisor_manager = HypervisorManager(dynamips_path, working_dir)
|
||||
|
||||
# apply settings to the hypervisor manager
|
||||
for name, value in request.items():
|
||||
@ -305,6 +342,28 @@ class Dynamips(IModule):
|
||||
router.ghost_status = 2
|
||||
router.ghost_file = ghost_instance
|
||||
|
||||
# def get_base64_config(self, config_path, router):
|
||||
# """
|
||||
# Get the base64 encoded config from a file.
|
||||
# Replaces %h by the router name.
|
||||
#
|
||||
# :param config_path: path to the configuration file.
|
||||
# :param router: Router instance.
|
||||
#
|
||||
# :returns: base64 encoded string
|
||||
# """
|
||||
#
|
||||
# try:
|
||||
# with open(config_path, "r") as f:
|
||||
# log.info("opening configuration file: {}".format(config_path))
|
||||
# config = f.read()
|
||||
# config = '!\n' + config.replace('\r', "")
|
||||
# config = config.replace('%h', router.name)
|
||||
# encoded = ("").join(base64.encodestring(config.encode("utf-8")).decode("utf-8").split())
|
||||
# return encoded
|
||||
# except EnvironmentError as e:
|
||||
# raise DynamipsError("Cannot parse {}: {}".format(config_path, e))
|
||||
|
||||
@IModule.route("dynamips.nio.get_interfaces")
|
||||
def nio_get_interfaces(self, request):
|
||||
"""
|
||||
|
@ -16,6 +16,7 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import base64
|
||||
from gns3server.modules import IModule
|
||||
from ..dynamips_error import DynamipsError
|
||||
|
||||
@ -121,8 +122,13 @@ class VM(object):
|
||||
platform = request["platform"]
|
||||
image = request["image"]
|
||||
ram = request["ram"]
|
||||
hypervisor = None
|
||||
|
||||
try:
|
||||
|
||||
if not self._hypervisor_manager:
|
||||
raise DynamipsError("Dynamips manager is not started")
|
||||
|
||||
hypervisor = self._hypervisor_manager.allocate_hypervisor_for_router(image, ram)
|
||||
|
||||
router = PLATFORMS[platform](hypervisor, name)
|
||||
@ -158,11 +164,14 @@ class VM(object):
|
||||
self.set_ghost_ios(router)
|
||||
|
||||
except DynamipsError as e:
|
||||
hypervisor.decrease_memory_load(ram)
|
||||
if hypervisor.memory_load == 0 and not hypervisor.devices:
|
||||
hypervisor.stop()
|
||||
self._hypervisor_manager.hypervisors.remove(hypervisor)
|
||||
self.send_custom_error(str(e))
|
||||
dynamips_stdout = ""
|
||||
if hypervisor:
|
||||
hypervisor.decrease_memory_load(ram)
|
||||
if hypervisor.memory_load == 0 and not hypervisor.devices:
|
||||
hypervisor.stop()
|
||||
self._hypervisor_manager.hypervisors.remove(hypervisor)
|
||||
dynamips_stdout = hypervisor.read_stdout()
|
||||
self.send_custom_error(str(e) + dynamips_stdout)
|
||||
return
|
||||
|
||||
response = {"name": router.name,
|
||||
@ -330,6 +339,7 @@ class VM(object):
|
||||
|
||||
Optional request parameters:
|
||||
- any setting to update
|
||||
- startup_config_base64 (startup-config base64 encoded)
|
||||
|
||||
Response parameters:
|
||||
- same as original request
|
||||
@ -346,6 +356,32 @@ class VM(object):
|
||||
router_id = request["id"]
|
||||
router = self._routers[router_id]
|
||||
|
||||
try:
|
||||
# a new startup-config has been pushed
|
||||
if "startup_config_base64" in request:
|
||||
config = base64.decodestring(request["startup_config_base64"].encode("utf-8")).decode("utf-8")
|
||||
config = "!\n" + config.replace("\r", "")
|
||||
config = config.replace('%h', router.name)
|
||||
config_dir = os.path.join(router.hypervisor.working_dir, "configs")
|
||||
if not os.path.exists(config_dir):
|
||||
try:
|
||||
os.makedirs(config_dir)
|
||||
except EnvironmentError as e:
|
||||
raise DynamipsError("Could not create configs directory: {}".format(e))
|
||||
config_path = os.path.join(config_dir, "{}.cfg".format(router.name))
|
||||
try:
|
||||
with open(config_path, "w") as f:
|
||||
log.info("saving startup-config to {}".format(config_path))
|
||||
f.write(config)
|
||||
except EnvironmentError as e:
|
||||
raise DynamipsError("Could not save the configuration {}: {}".format(config_path, e))
|
||||
request["startup_config"] = "configs" + os.sep + os.path.basename(config_path)
|
||||
if "startup_config" in request:
|
||||
router.set_config(request["startup_config"])
|
||||
except DynamipsError as e:
|
||||
self.send_custom_error(str(e))
|
||||
return
|
||||
|
||||
# update the settings
|
||||
for name, value in request.items():
|
||||
if hasattr(router, name) and getattr(router, name) != value:
|
||||
@ -370,7 +406,7 @@ class VM(object):
|
||||
if router.slots[slot_id]:
|
||||
try:
|
||||
router.slot_remove_binding(slot_id)
|
||||
except:
|
||||
except DynamipsError as e:
|
||||
self.send_custom_error(str(e))
|
||||
return
|
||||
elif name.startswith("wic") and value in WIC_MATRIX:
|
||||
@ -387,7 +423,11 @@ class VM(object):
|
||||
elif name.startswith("wic") and value == None:
|
||||
wic_slot_id = int(name[-1])
|
||||
if router.slots[0].wics and router.slots[0].wics[wic_slot_id]:
|
||||
router.uninstall_wic(wic_slot_id)
|
||||
try:
|
||||
router.uninstall_wic(wic_slot_id)
|
||||
except DynamipsError as e:
|
||||
self.send_custom_error(str(e))
|
||||
return
|
||||
|
||||
# Update the ghost IOS file in case the RAM size has changed
|
||||
if self._hypervisor_manager.ghost_ios_support:
|
||||
@ -396,6 +436,40 @@ class VM(object):
|
||||
# for now send back the original request
|
||||
self.send_response(request)
|
||||
|
||||
@IModule.route("dynamips.vm.save_config")
|
||||
def vm_save_config(self, request):
|
||||
"""
|
||||
Save the configs for a VM (router).
|
||||
|
||||
Mandatory request parameters:
|
||||
- id (vm identifier)
|
||||
"""
|
||||
|
||||
if request == None:
|
||||
self.send_param_error()
|
||||
return
|
||||
|
||||
#TODO: JSON schema validation for the request
|
||||
log.debug("received request {}".format(request))
|
||||
router_id = request["id"]
|
||||
router = self._routers[router_id]
|
||||
try:
|
||||
if router.startup_config:
|
||||
#TODO: handle private-config
|
||||
startup_config_base64, _ = router.extract_config()
|
||||
if startup_config_base64:
|
||||
try:
|
||||
config = base64.decodestring(startup_config_base64.encode("utf-8")).decode("utf-8")
|
||||
config = "!\n" + config.replace("\r", "")
|
||||
config_path = os.path.join(router.hypervisor.working_dir, router.startup_config)
|
||||
with open(config_path, "w") as f:
|
||||
log.info("saving startup-config to {}".format(router.startup_config))
|
||||
f.write(config)
|
||||
except EnvironmentError as e:
|
||||
raise DynamipsError("Could not save the configuration {}: {}".format(config_path, e))
|
||||
except DynamipsError as e:
|
||||
log.warn("could not save config to {}: {}".format(router.startup_config, e))
|
||||
|
||||
@IModule.route("dynamips.vm.idlepcs")
|
||||
def vm_idlepcs(self, request):
|
||||
"""
|
||||
|
@ -33,6 +33,7 @@ class DynamipsHypervisor(object):
|
||||
"""
|
||||
Creates a new connection to a Dynamips server (also called hypervisor)
|
||||
|
||||
:param working_dir: working directory
|
||||
:param host: the hostname or ip address string of the Dynamips server
|
||||
:param port: the tcp port integer (defaults to 7200)
|
||||
:param timeout: timeout integer for how long to wait for a response to commands sent to the
|
||||
@ -43,7 +44,7 @@ class DynamipsHypervisor(object):
|
||||
error_re = re.compile(r"""^2[0-9]{2}-""")
|
||||
success_re = re.compile(r"""^1[0-9]{2}\s{1}""")
|
||||
|
||||
def __init__(self, host, port=7200, timeout=30.0):
|
||||
def __init__(self, working_dir, host, port=7200, timeout=30.0):
|
||||
|
||||
self._host = host
|
||||
self._port = port
|
||||
@ -51,7 +52,7 @@ class DynamipsHypervisor(object):
|
||||
self._devices = []
|
||||
self._ghosts = {}
|
||||
self._jitsharing_groups = {}
|
||||
self._working_dir = ""
|
||||
self._working_dir = working_dir
|
||||
self._baseconsole = 2000
|
||||
self._baseaux = 2100
|
||||
self._baseudp = 10000
|
||||
@ -155,6 +156,7 @@ class DynamipsHypervisor(object):
|
||||
# encase working_dir in quotes to protect spaces in the path
|
||||
self.send("hypervisor working_dir {}".format('"' + working_dir + '"'))
|
||||
self._working_dir = working_dir
|
||||
log.debug("working directory set to {}".format(self._working_dir))
|
||||
|
||||
def save_config(self, filename):
|
||||
"""
|
||||
@ -332,6 +334,40 @@ class DynamipsHypervisor(object):
|
||||
|
||||
return self._port
|
||||
|
||||
@staticmethod
|
||||
def find_unused_port(start_port, end_port, host='127.0.0.1', socket_type="TCP"):
|
||||
"""
|
||||
Finds an unused port in a range.
|
||||
|
||||
:param start_port: first port in the range
|
||||
:param end_port: last port in the range
|
||||
:param host: host/address for bind()
|
||||
:param socket_type: TCP (default) or UDP
|
||||
"""
|
||||
|
||||
if socket_type == "UDP":
|
||||
socket_type = socket.SOCK_DGRAM
|
||||
else:
|
||||
socket_type = socket.SOCK_STREAM
|
||||
|
||||
for port in range(start_port, end_port):
|
||||
if port > end_port:
|
||||
raise DynamipsError("Could not find a free port between {0} and {1}".format(start_port, end_port))
|
||||
try:
|
||||
if ":" in host:
|
||||
# IPv6 address support
|
||||
s = socket.socket(socket.AF_INET6, socket_type)
|
||||
else:
|
||||
s = socket.socket(socket.AF_INET, socket_type)
|
||||
# the port is available if bind is a success
|
||||
s.bind((host, port))
|
||||
return port
|
||||
except socket.error as e:
|
||||
if e.errno == errno.EADDRINUSE: # socket already in use
|
||||
continue
|
||||
else:
|
||||
raise DynamipsError("Could not find an unused port: {}".format(e))
|
||||
|
||||
def allocate_udp_port(self, max_port=100):
|
||||
"""
|
||||
Allocates a new UDP port for creating an UDP NIO.
|
||||
@ -342,28 +378,14 @@ class DynamipsHypervisor(object):
|
||||
:returns: port number (integer)
|
||||
"""
|
||||
|
||||
#FIXME: better check for IPv6
|
||||
start_port = self._current_udp_port
|
||||
end_port = start_port + max_port
|
||||
for port in range(start_port, end_port):
|
||||
if port > end_port:
|
||||
raise DynamipsError("Could not find a free port between {0} and {1}".format(start_port, max_port))
|
||||
try:
|
||||
if self.host.__contains__(':'):
|
||||
# IPv6 address support
|
||||
s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
|
||||
else:
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
# the port is available if bind is a success
|
||||
s.bind((self._host, port))
|
||||
#FIXME: increment?
|
||||
self._current_udp_port += 1
|
||||
return port
|
||||
except socket.error as e:
|
||||
if e.errno == errno.EADDRINUSE: # socket already in use
|
||||
continue
|
||||
else:
|
||||
raise DynamipsError("UDP port allocation: {}".format(e))
|
||||
allocated_port = DynamipsHypervisor.find_unused_port(start_port, end_port, self._host, socket_type="UDP")
|
||||
if allocated_port - self._current_udp_port > 1:
|
||||
self._current_udp_port += allocated_port - self._current_udp_port
|
||||
else:
|
||||
self._current_udp_port += 1
|
||||
return allocated_port
|
||||
|
||||
def send_raw(self, string):
|
||||
"""
|
||||
|
@ -22,11 +22,12 @@ Represents a Dynamips hypervisor and starts/stops the associated Dynamips proces
|
||||
import os
|
||||
import time
|
||||
import subprocess
|
||||
import logging
|
||||
|
||||
from .dynamips_hypervisor import DynamipsHypervisor
|
||||
from .dynamips_error import DynamipsError
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Hypervisor(DynamipsHypervisor):
|
||||
@ -34,26 +35,25 @@ class Hypervisor(DynamipsHypervisor):
|
||||
Hypervisor.
|
||||
|
||||
:param path: path to Dynamips executable
|
||||
:param workingdir: working directory
|
||||
:param working_dir: working directory
|
||||
:param port: port for this hypervisor
|
||||
:param host: host/address for this hypervisor
|
||||
"""
|
||||
|
||||
_instance_count = 0
|
||||
|
||||
def __init__(self, path, workingdir, host, port):
|
||||
def __init__(self, path, working_dir, host, port):
|
||||
|
||||
DynamipsHypervisor.__init__(self, host, port)
|
||||
DynamipsHypervisor.__init__(self, working_dir, host, port)
|
||||
|
||||
# create an unique ID
|
||||
self._id = Hypervisor._instance_count
|
||||
Hypervisor._instance_count += 1
|
||||
|
||||
self._path = path
|
||||
self._workingdir = workingdir
|
||||
self._command = []
|
||||
self._process = None
|
||||
self._stdout = None
|
||||
self._stdout_file = ""
|
||||
|
||||
# settings used the load-balance hypervisors
|
||||
# (for the hypervisor manager)
|
||||
@ -130,26 +130,6 @@ class Hypervisor(DynamipsHypervisor):
|
||||
|
||||
self._host = host
|
||||
|
||||
@property
|
||||
def workingdir(self):
|
||||
"""
|
||||
Returns the working directory used to start the Dynamips hypervisor.
|
||||
|
||||
:returns: path to a working directory
|
||||
"""
|
||||
|
||||
return(self._workingdir)
|
||||
|
||||
@workingdir.setter
|
||||
def workingdir(self, workingdir):
|
||||
"""
|
||||
Sets the working directory used to start the Dynamips hypervisor.
|
||||
|
||||
:param workingdir: path to a working directory
|
||||
"""
|
||||
|
||||
self._workingdir = workingdir
|
||||
|
||||
@property
|
||||
def image_ref(self):
|
||||
"""
|
||||
@ -210,20 +190,18 @@ class Hypervisor(DynamipsHypervisor):
|
||||
|
||||
self._command = self._build_command()
|
||||
try:
|
||||
logger.info("Starting Dynamips: {}".format(self._command))
|
||||
# TODO: create unique filename for stdout
|
||||
self.stdout_file = os.path.join(self._workingdir, "dynamips.log")
|
||||
fd = open(self.stdout_file, "w")
|
||||
# TODO: check for exceptions and if process has already been started
|
||||
self._process = subprocess.Popen(self._command,
|
||||
stdout=fd,
|
||||
stderr=subprocess.STDOUT,
|
||||
cwd=self._workingdir)
|
||||
logger.info("Dynamips started PID={}".format(self._process.pid))
|
||||
except OSError as e:
|
||||
logger.error("Could not start Dynamips: {}".format(e))
|
||||
finally:
|
||||
fd.close()
|
||||
log.info("starting Dynamips: {}".format(self._command))
|
||||
self._stdout_file = os.path.join(self._working_dir, "dynamips-{}.log".format(self._port))
|
||||
log.info("logging to {}".format(self._stdout_file))
|
||||
with open(self._stdout_file, "w") as fd:
|
||||
self._process = subprocess.Popen(self._command,
|
||||
stdout=fd,
|
||||
stderr=subprocess.STDOUT,
|
||||
cwd=self._working_dir)
|
||||
log.info("Dynamips started PID={}".format(self._process.pid))
|
||||
except EnvironmentError as e:
|
||||
log.error("could not start Dynamips: {}".format(e))
|
||||
raise DynamipsError("could not start Dynamips: {}".format(e))
|
||||
|
||||
def stop(self):
|
||||
"""
|
||||
@ -232,7 +210,7 @@ class Hypervisor(DynamipsHypervisor):
|
||||
|
||||
if self.is_running():
|
||||
DynamipsHypervisor.stop(self)
|
||||
logger.info("Stopping Dynamips PID={}".format(self._process.pid))
|
||||
log.info("stopping Dynamips PID={}".format(self._process.pid))
|
||||
# give some time for the hypervisor to properly stop.
|
||||
# time to delete UNIX NIOs for instance.
|
||||
time.sleep(0.01)
|
||||
@ -245,9 +223,13 @@ class Hypervisor(DynamipsHypervisor):
|
||||
Only use when the process has been stopped or has crashed.
|
||||
"""
|
||||
|
||||
# TODO: check for exceptions
|
||||
with open(self.stdout_file) as file:
|
||||
output = file.read()
|
||||
output = ""
|
||||
if self._stdout_file:
|
||||
try:
|
||||
with open(self._stdout_file) as file:
|
||||
output = file.read()
|
||||
except EnvironmentError as e:
|
||||
log.warn("could not read {}: {}".format(self._stdout_file, e))
|
||||
return output
|
||||
|
||||
def is_running(self):
|
||||
|
@ -19,8 +19,8 @@
|
||||
Manages Dynamips hypervisors (load-balancing etc.)
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from .hypervisor import Hypervisor
|
||||
from .dynamips_error import DynamipsError
|
||||
import socket
|
||||
import time
|
||||
import logging
|
||||
@ -33,7 +33,7 @@ class HypervisorManager(object):
|
||||
Manages Dynamips hypervisors.
|
||||
|
||||
:param path: path to the Dynamips executable
|
||||
:param workingdir: path to a working directory
|
||||
:param working_dir: path to a working directory
|
||||
:param host: host/address for hypervisors to listen to
|
||||
:param base_port: base TCP port for hypervisors
|
||||
:param base_console: base TCP port for consoles
|
||||
@ -43,7 +43,7 @@ class HypervisorManager(object):
|
||||
|
||||
def __init__(self,
|
||||
path,
|
||||
workingdir,
|
||||
working_dir,
|
||||
host='127.0.0.1',
|
||||
base_hypervisor_port=7200,
|
||||
base_console_port=2000,
|
||||
@ -52,7 +52,7 @@ class HypervisorManager(object):
|
||||
|
||||
self._hypervisors = []
|
||||
self._path = path
|
||||
self._workingdir = workingdir
|
||||
self._working_dir = working_dir
|
||||
self._host = host
|
||||
self._base_hypervisor_port = base_hypervisor_port
|
||||
self._current_port = self._base_hypervisor_port
|
||||
@ -108,25 +108,29 @@ class HypervisorManager(object):
|
||||
log.info("Dynamips path set to {}".format(self._path))
|
||||
|
||||
@property
|
||||
def workingdir(self):
|
||||
def working_dir(self):
|
||||
"""
|
||||
Returns the Dynamips working directory path.
|
||||
|
||||
:returns: path to Dynamips working directory
|
||||
"""
|
||||
|
||||
return self._workingdir
|
||||
return self._working_dir
|
||||
|
||||
@workingdir.setter
|
||||
def workingdir(self, workingdir):
|
||||
@working_dir.setter
|
||||
def working_dir(self, working_dir):
|
||||
"""
|
||||
Sets a new path to the Dynamips working directory.
|
||||
|
||||
:param workingdir: path to Dynamips working directory
|
||||
:param working_dir: path to Dynamips working directory
|
||||
"""
|
||||
|
||||
self._workingdir = workingdir
|
||||
log.info("working directory set to {}".format(self._workingdir))
|
||||
self._working_dir = working_dir
|
||||
log.info("working directory set to {}".format(self._working_dir))
|
||||
|
||||
# update all existing hypervisors with the new working directory
|
||||
for hypervisor in self._hypervisors:
|
||||
hypervisor.working_dir = working_dir
|
||||
|
||||
@property
|
||||
def base_hypervisor_port(self):
|
||||
@ -406,16 +410,12 @@ class HypervisorManager(object):
|
||||
# try to connect for 10 seconds
|
||||
while(time.time() - begin < 10.0):
|
||||
time.sleep(0.01)
|
||||
sock = None
|
||||
try:
|
||||
sock = socket.create_connection((host, port), timeout)
|
||||
with socket.create_connection((host, port), timeout):
|
||||
pass
|
||||
except socket.error as e:
|
||||
last_exception = e
|
||||
#time.sleep(0.01)
|
||||
continue
|
||||
finally:
|
||||
if sock:
|
||||
sock.close()
|
||||
connection_success = True
|
||||
break
|
||||
|
||||
@ -426,6 +426,25 @@ class HypervisorManager(object):
|
||||
else:
|
||||
log.info("Dynamips server ready after {:.4f} seconds".format(time.time() - begin))
|
||||
|
||||
def allocate_tcp_port(self, max_port=100):
|
||||
"""
|
||||
Allocates a new TCP port for a Dynamips hypervisor.
|
||||
|
||||
:param max_port: maximum number of port to scan in
|
||||
order to find one available for use.
|
||||
|
||||
:returns: port number (integer)
|
||||
"""
|
||||
|
||||
start_port = self._current_port
|
||||
end_port = start_port + max_port
|
||||
allocated_port = Hypervisor.find_unused_port(start_port, end_port, self._host)
|
||||
if allocated_port - self._current_port > 1:
|
||||
self._current_port += allocated_port - self._current_port
|
||||
else:
|
||||
self._current_port += 1
|
||||
return allocated_port
|
||||
|
||||
def start_new_hypervisor(self):
|
||||
"""
|
||||
Creates a new Dynamips process and start it.
|
||||
@ -433,15 +452,23 @@ class HypervisorManager(object):
|
||||
:returns: the new hypervisor instance
|
||||
"""
|
||||
|
||||
port = self.allocate_tcp_port()
|
||||
# working_dir = os.path.join(self._working_dir, "instance-{}".format(port))
|
||||
# if not os.path.exists(working_dir):
|
||||
# try:
|
||||
# os.makedirs(working_dir)
|
||||
# except EnvironmentError as e:
|
||||
# raise DynamipsError("{}".format(e))
|
||||
|
||||
hypervisor = Hypervisor(self._path,
|
||||
self._workingdir,
|
||||
self._working_dir,
|
||||
self._host,
|
||||
self._current_port)
|
||||
port)
|
||||
|
||||
log.info("creating new hypervisor {}:{}".format(hypervisor.host, hypervisor.port))
|
||||
hypervisor.start()
|
||||
|
||||
self.wait_for_hypervisor(self._host, self._current_port)
|
||||
self.wait_for_hypervisor(self._host, port)
|
||||
log.info("hypervisor {}:{} has successfully started".format(hypervisor.host, hypervisor.port))
|
||||
|
||||
hypervisor.connect()
|
||||
@ -450,7 +477,6 @@ class HypervisorManager(object):
|
||||
hypervisor.baseudp = self._current_base_udp_port
|
||||
self._current_base_udp_port += self._udp_incrementation_per_hypervisor
|
||||
self._hypervisors.append(hypervisor)
|
||||
self._current_port += 1
|
||||
return hypervisor
|
||||
|
||||
def allocate_hypervisor_for_router(self, router_ios_image, router_ram):
|
||||
|
@ -20,7 +20,6 @@ Interface for Dynamips virtual ATM bridge module ("atm_bridge").
|
||||
http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L622
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from ..dynamips_error import DynamipsError
|
||||
|
||||
|
||||
|
@ -20,7 +20,6 @@ Interface for Dynamips virtual ATM switch module ("atmsw").
|
||||
http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L593
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from ..dynamips_error import DynamipsError
|
||||
|
||||
import logging
|
||||
|
@ -20,8 +20,6 @@ Interface for Dynamips NIO bridge module ("nio_bridge").
|
||||
http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L538
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
||||
class Bridge(object):
|
||||
"""
|
||||
@ -36,6 +34,7 @@ class Bridge(object):
|
||||
def __init__(self, hypervisor, name):
|
||||
|
||||
self._hypervisor = hypervisor
|
||||
self._allocated_names.append(name)
|
||||
self._name = '"' + name + '"' # put name into quotes to protect spaces
|
||||
self._hypervisor.send("nio_bridge create {}".format(self._name))
|
||||
self._hypervisor.devices.append(self)
|
||||
|
@ -20,7 +20,6 @@ Interface for Dynamips virtual Cisco 1700 instances module ("c1700")
|
||||
http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L428
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from .router import Router
|
||||
from ..adapters.c1700_mb_1fe import C1700_MB_1FE
|
||||
from ..adapters.c1700_mb_wic1 import C1700_MB_WIC1
|
||||
|
@ -20,7 +20,6 @@ Interface for Dynamips virtual Cisco 2600 instances module ("c2600")
|
||||
http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L404
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from .router import Router
|
||||
from ..adapters.c2600_mb_1e import C2600_MB_1E
|
||||
from ..adapters.c2600_mb_2e import C2600_MB_2E
|
||||
|
@ -20,7 +20,6 @@ Interface for Dynamips virtual Cisco 2691 instances module ("c2691")
|
||||
http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L387
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from .router import Router
|
||||
from ..adapters.gt96100_fe import GT96100_FE
|
||||
|
||||
|
@ -20,7 +20,6 @@ Interface for Dynamips virtual Cisco 3600 instances module ("c3600")
|
||||
http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L366
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from .router import Router
|
||||
from ..adapters.leopard_2fe import Leopard_2FE
|
||||
|
||||
|
@ -20,7 +20,6 @@ Interface for Dynamips virtual Cisco 3725 instances module ("c3725")
|
||||
http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L346
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from .router import Router
|
||||
from ..adapters.gt96100_fe import GT96100_FE
|
||||
|
||||
|
@ -20,7 +20,6 @@ Interface for Dynamips virtual Cisco 3745 instances module ("c3745")
|
||||
http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L326
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from .router import Router
|
||||
from ..adapters.gt96100_fe import GT96100_FE
|
||||
|
||||
|
@ -20,7 +20,6 @@ Interface for Dynamips virtual Cisco 7200 instances module ("c7200")
|
||||
http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L294
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from ..dynamips_error import DynamipsError
|
||||
from .router import Router
|
||||
from ..adapters.c7200_io_2fe import C7200_IO_2FE
|
||||
|
@ -20,8 +20,6 @@ Interface for Dynamips virtual Ethernet switch module ("ethsw").
|
||||
http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L558
|
||||
"""
|
||||
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from ..dynamips_error import DynamipsError
|
||||
|
||||
import logging
|
||||
|
@ -20,7 +20,6 @@ Interface for Dynamips virtual Frame-Relay switch module.
|
||||
http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L642
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from ..dynamips_error import DynamipsError
|
||||
|
||||
import logging
|
||||
|
@ -19,7 +19,6 @@
|
||||
Hub object that uses the Bridge interface to create a hub with ports.
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from .bridge import Bridge
|
||||
from ..dynamips_error import DynamipsError
|
||||
|
||||
@ -53,7 +52,6 @@ class Hub(Bridge):
|
||||
break
|
||||
name_id += 1
|
||||
|
||||
self._allocated_names.append(name)
|
||||
self._mapping = {}
|
||||
Bridge.__init__(self, hypervisor, name)
|
||||
|
||||
|
@ -20,7 +20,7 @@ Interface for Dynamips virtual Machine module ("vm")
|
||||
http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L77
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from ..dynamips_hypervisor import DynamipsHypervisor
|
||||
from ..dynamips_error import DynamipsError
|
||||
import time
|
||||
import sys
|
||||
@ -68,6 +68,8 @@ class Router(object):
|
||||
self._name = '"' + name + '"' # put name into quotes to protect spaces
|
||||
self._platform = platform
|
||||
self._image = ""
|
||||
self._startup_config = ""
|
||||
self._private_config = ""
|
||||
self._ram = 128 # Megabytes
|
||||
self._nvram = 128 # Kilobytes
|
||||
self._mmap = True
|
||||
@ -100,8 +102,12 @@ class Router(object):
|
||||
log.info("router {platform} {name} [id={id}] has been created".format(name=self._name,
|
||||
platform=platform,
|
||||
id=self._id))
|
||||
self.console = self._hypervisor.baseconsole + self._id
|
||||
self.aux = self._hypervisor.baseaux + self._id
|
||||
|
||||
# allocate and check that console and aux ports are unused
|
||||
console_port = (self._hypervisor.baseconsole - 1) + self._id
|
||||
self.console = DynamipsHypervisor.find_unused_port(console_port, console_port + 1, self._hypervisor.host)
|
||||
aux_port = (self._hypervisor.baseaux - 1) + self._id
|
||||
self.aux = DynamipsHypervisor.find_unused_port(aux_port, aux_port + 1, self._hypervisor.host)
|
||||
|
||||
# get the default base MAC address
|
||||
self._mac_addr = self._hypervisor.send("{platform} get_mac_addr {name}".format(platform=self._platform,
|
||||
@ -130,6 +136,8 @@ class Router(object):
|
||||
|
||||
router_defaults = {"platform": self._platform,
|
||||
"image": self._image,
|
||||
"startup_config": self._startup_config,
|
||||
"private_config": self._private_config,
|
||||
"ram": self._ram,
|
||||
"nvram": self._nvram,
|
||||
"mmap": self._mmap,
|
||||
@ -249,7 +257,7 @@ class Router(object):
|
||||
Deletes this router.
|
||||
"""
|
||||
|
||||
self._hypervisor.send("vm delete {}".format(self._name))
|
||||
self._hypervisor.send("vm clean_delete {}".format(self._name))
|
||||
self._hypervisor.devices.remove(self)
|
||||
|
||||
log.info("router {name} [id={id}] has been deleted".format(name=self._name, id=self._id))
|
||||
@ -384,6 +392,46 @@ class Router(object):
|
||||
|
||||
self._image = image
|
||||
|
||||
@property
|
||||
def startup_config(self):
|
||||
"""
|
||||
Returns the startup-config for this router.
|
||||
|
||||
:returns: path to startup-config file
|
||||
"""
|
||||
|
||||
return self._startup_config
|
||||
|
||||
@startup_config.setter
|
||||
def startup_config(self, startup_config):
|
||||
"""
|
||||
Sets the startup-config for this router.
|
||||
|
||||
:param startup_config: path to startup-config file
|
||||
"""
|
||||
|
||||
self._startup_config = startup_config
|
||||
|
||||
@property
|
||||
def private_config(self):
|
||||
"""
|
||||
Returns the private-config for this router.
|
||||
|
||||
:returns: path to private-config file
|
||||
"""
|
||||
|
||||
return self._private_config
|
||||
|
||||
@private_config.setter
|
||||
def private_config(self, private_config):
|
||||
"""
|
||||
Sets the private-config for this router.
|
||||
|
||||
:param private_config: path to private-config file
|
||||
"""
|
||||
|
||||
self._private_config = private_config
|
||||
|
||||
def set_config(self, startup_config, private_config=''):
|
||||
"""
|
||||
Sets the config files that are pushed to startup-config and
|
||||
@ -394,18 +442,20 @@ class Router(object):
|
||||
(keep existing data when if an empty string)
|
||||
"""
|
||||
|
||||
self._hypervisor.send("vm set_config {name} {startup} {private}".format(name=self._name,
|
||||
startup='"' + startup_config + '"',
|
||||
private='"' + private_config + '"'))
|
||||
if self._startup_config != startup_config or self._private_config != private_config:
|
||||
|
||||
log.info("router {name} [id={id}]: has a startup-config set: {startup}".format(name=self._name,
|
||||
id=self._id,
|
||||
startup='"' + startup_config + '"'))
|
||||
self._hypervisor.send("vm set_config {name} {startup} {private}".format(name=self._name,
|
||||
startup='"' + startup_config + '"',
|
||||
private='"' + private_config + '"'))
|
||||
|
||||
if private_config:
|
||||
log.info("router {name} [id={id}]: has a private-config set: {private}".format(name=self._name,
|
||||
log.info("router {name} [id={id}]: has a startup-config set: {startup}".format(name=self._name,
|
||||
id=self._id,
|
||||
private='"' + private_config + '"'))
|
||||
startup='"' + startup_config + '"'))
|
||||
|
||||
if private_config:
|
||||
log.info("router {name} [id={id}]: has a private-config set: {private}".format(name=self._name,
|
||||
id=self._id,
|
||||
private='"' + private_config + '"'))
|
||||
|
||||
def extract_config(self):
|
||||
"""
|
||||
|
@ -49,9 +49,16 @@ class Server(object):
|
||||
self._host = host
|
||||
self._port = port
|
||||
if ipc:
|
||||
self._zmq_port = 0 # this forces module to use IPC for communications
|
||||
self._zmq_port = 0 # this forces to use IPC for communications with the ZeroMQ server
|
||||
else:
|
||||
self._zmq_port = port + 1 # this server port + 1
|
||||
try:
|
||||
# let the OS find an unused port for the ZeroMQ server
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
||||
sock.bind(('127.0.0.1', 0))
|
||||
self._zmq_port = sock.getsockname()[1]
|
||||
except socket.error as e:
|
||||
log.warn("could not pick up a random port for the ZeroMQ server: {}".format(e))
|
||||
self._zmq_port = port + 1 # let's try this server port + 1
|
||||
self._ipc = ipc
|
||||
self._modules = []
|
||||
|
||||
|
Binary file not shown.
@ -18,9 +18,9 @@ def test_host(hypervisor):
|
||||
assert hypervisor.host == "127.0.0.1"
|
||||
|
||||
|
||||
def test_workingdir(hypervisor):
|
||||
def test_working_dir(hypervisor):
|
||||
|
||||
assert hypervisor.workingdir == "/tmp"
|
||||
assert hypervisor.working_dir == "/tmp"
|
||||
|
||||
|
||||
def test_path(hypervisor):
|
||||
|
Loading…
Reference in New Issue
Block a user