1
0
mirror of https://github.com/GNS3/gns3-server synced 2024-11-24 09:18:08 +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:
grossmj 2014-03-02 15:20:03 -07:00
parent 687d5b75ab
commit 89888ae7bf
25 changed files with 349 additions and 137 deletions

View File

@ -82,8 +82,8 @@ class ModuleManager(object):
log.info("loading {} module".format(module_class[0].lower())) log.info("loading {} module".format(module_class[0].lower()))
info = Module(name=module_class[0].lower(), cls=module_class[1]) info = Module(name=module_class[0].lower(), cls=module_class[1])
self._modules.append(info) self._modules.append(info)
except: except Exception as e:
log.warning("error while analyzing {} package directory".format(name)) log.critical("error while analyzing {} package directory".format(name), exc_info=1)
finally: finally:
if file: if file:
file.close() file.close()

View File

@ -93,9 +93,16 @@ class IModule(multiprocessing.Process):
stream.on_recv(callback) stream.on_recv(callback)
return stream return stream
# def add_periodic_callback(self, callback, time): def add_periodic_callback(self, callback, time):
# """
# self.test = zmq.eventloop.ioloop.PeriodicCallback(callback, time, self._ioloop).start() 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): def run(self):
""" """

View File

@ -19,6 +19,8 @@
Dynamips server module. Dynamips server module.
""" """
import os
import tempfile
from gns3server.modules import IModule from gns3server.modules import IModule
import gns3server.jsonrpc as jsonrpc import gns3server.jsonrpc as jsonrpc
@ -99,12 +101,16 @@ class Dynamips(IModule):
IModule.__init__(self, name=name, args=args, kwargs=kwargs) IModule.__init__(self, name=name, args=args, kwargs=kwargs)
self._hypervisor_manager = None self._hypervisor_manager = None
self._remote_server = False
self._routers = {} self._routers = {}
self._ethernet_switches = {} self._ethernet_switches = {}
self._frame_relay_switches = {} self._frame_relay_switches = {}
self._atm_switches = {} self._atm_switches = {}
self._ethernet_hubs = {} self._ethernet_hubs = {}
#self._callback = self.add_periodic_callback(self.test, 1000)
#self._callback.start()
def stop(self): def stop(self):
""" """
Properly stops the module. Properly stops the module.
@ -148,6 +154,8 @@ class Dynamips(IModule):
self._frame_relay_switches.clear() self._frame_relay_switches.clear()
self._atm_switches.clear() self._atm_switches.clear()
self._hypervisor_manager = None
self._remote_server = False
log.info("dynamips module has been reset") log.info("dynamips module has been reset")
@IModule.route("dynamips.settings") @IModule.route("dynamips.settings")
@ -155,6 +163,12 @@ class Dynamips(IModule):
""" """
Set or update settings. 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 :param request: JSON request
""" """
@ -167,9 +181,32 @@ class Dynamips(IModule):
#TODO: JSON schema validation #TODO: JSON schema validation
# starts the hypervisor manager if it hasn't been started yet # starts the hypervisor manager if it hasn't been started yet
if not self._hypervisor_manager: if not self._hypervisor_manager:
#TODO: working dir support dynamips_path = request["path"]
log.info("starting the hypervisor manager with Dynamips working directory set to '{}'".format("/tmp"))
self._hypervisor_manager = HypervisorManager(request["path"], "/tmp") 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 # apply settings to the hypervisor manager
for name, value in request.items(): for name, value in request.items():
@ -305,6 +342,28 @@ class Dynamips(IModule):
router.ghost_status = 2 router.ghost_status = 2
router.ghost_file = ghost_instance 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") @IModule.route("dynamips.nio.get_interfaces")
def nio_get_interfaces(self, request): def nio_get_interfaces(self, request):
""" """

View File

@ -16,6 +16,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import os import os
import base64
from gns3server.modules import IModule from gns3server.modules import IModule
from ..dynamips_error import DynamipsError from ..dynamips_error import DynamipsError
@ -121,8 +122,13 @@ class VM(object):
platform = request["platform"] platform = request["platform"]
image = request["image"] image = request["image"]
ram = request["ram"] ram = request["ram"]
hypervisor = None
try: try:
if not self._hypervisor_manager:
raise DynamipsError("Dynamips manager is not started")
hypervisor = self._hypervisor_manager.allocate_hypervisor_for_router(image, ram) hypervisor = self._hypervisor_manager.allocate_hypervisor_for_router(image, ram)
router = PLATFORMS[platform](hypervisor, name) router = PLATFORMS[platform](hypervisor, name)
@ -158,11 +164,14 @@ class VM(object):
self.set_ghost_ios(router) self.set_ghost_ios(router)
except DynamipsError as e: except DynamipsError as e:
dynamips_stdout = ""
if hypervisor:
hypervisor.decrease_memory_load(ram) hypervisor.decrease_memory_load(ram)
if hypervisor.memory_load == 0 and not hypervisor.devices: if hypervisor.memory_load == 0 and not hypervisor.devices:
hypervisor.stop() hypervisor.stop()
self._hypervisor_manager.hypervisors.remove(hypervisor) self._hypervisor_manager.hypervisors.remove(hypervisor)
self.send_custom_error(str(e)) dynamips_stdout = hypervisor.read_stdout()
self.send_custom_error(str(e) + dynamips_stdout)
return return
response = {"name": router.name, response = {"name": router.name,
@ -330,6 +339,7 @@ class VM(object):
Optional request parameters: Optional request parameters:
- any setting to update - any setting to update
- startup_config_base64 (startup-config base64 encoded)
Response parameters: Response parameters:
- same as original request - same as original request
@ -346,6 +356,32 @@ class VM(object):
router_id = request["id"] router_id = request["id"]
router = self._routers[router_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 # update the settings
for name, value in request.items(): for name, value in request.items():
if hasattr(router, name) and getattr(router, name) != value: if hasattr(router, name) and getattr(router, name) != value:
@ -370,7 +406,7 @@ class VM(object):
if router.slots[slot_id]: if router.slots[slot_id]:
try: try:
router.slot_remove_binding(slot_id) router.slot_remove_binding(slot_id)
except: except DynamipsError as e:
self.send_custom_error(str(e)) self.send_custom_error(str(e))
return return
elif name.startswith("wic") and value in WIC_MATRIX: elif name.startswith("wic") and value in WIC_MATRIX:
@ -387,7 +423,11 @@ class VM(object):
elif name.startswith("wic") and value == None: elif name.startswith("wic") and value == None:
wic_slot_id = int(name[-1]) wic_slot_id = int(name[-1])
if router.slots[0].wics and router.slots[0].wics[wic_slot_id]: if router.slots[0].wics and router.slots[0].wics[wic_slot_id]:
try:
router.uninstall_wic(wic_slot_id) 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 # Update the ghost IOS file in case the RAM size has changed
if self._hypervisor_manager.ghost_ios_support: if self._hypervisor_manager.ghost_ios_support:
@ -396,6 +436,40 @@ class VM(object):
# for now send back the original request # for now send back the original request
self.send_response(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") @IModule.route("dynamips.vm.idlepcs")
def vm_idlepcs(self, request): def vm_idlepcs(self, request):
""" """

View File

@ -33,6 +33,7 @@ class DynamipsHypervisor(object):
""" """
Creates a new connection to a Dynamips server (also called hypervisor) 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 host: the hostname or ip address string of the Dynamips server
:param port: the tcp port integer (defaults to 7200) :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 :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}-""") error_re = re.compile(r"""^2[0-9]{2}-""")
success_re = re.compile(r"""^1[0-9]{2}\s{1}""") 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._host = host
self._port = port self._port = port
@ -51,7 +52,7 @@ class DynamipsHypervisor(object):
self._devices = [] self._devices = []
self._ghosts = {} self._ghosts = {}
self._jitsharing_groups = {} self._jitsharing_groups = {}
self._working_dir = "" self._working_dir = working_dir
self._baseconsole = 2000 self._baseconsole = 2000
self._baseaux = 2100 self._baseaux = 2100
self._baseudp = 10000 self._baseudp = 10000
@ -155,6 +156,7 @@ class DynamipsHypervisor(object):
# encase working_dir in quotes to protect spaces in the path # encase working_dir in quotes to protect spaces in the path
self.send("hypervisor working_dir {}".format('"' + working_dir + '"')) self.send("hypervisor working_dir {}".format('"' + working_dir + '"'))
self._working_dir = working_dir self._working_dir = working_dir
log.debug("working directory set to {}".format(self._working_dir))
def save_config(self, filename): def save_config(self, filename):
""" """
@ -332,6 +334,40 @@ class DynamipsHypervisor(object):
return self._port 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): def allocate_udp_port(self, max_port=100):
""" """
Allocates a new UDP port for creating an UDP NIO. Allocates a new UDP port for creating an UDP NIO.
@ -342,28 +378,14 @@ class DynamipsHypervisor(object):
:returns: port number (integer) :returns: port number (integer)
""" """
#FIXME: better check for IPv6
start_port = self._current_udp_port start_port = self._current_udp_port
end_port = start_port + max_port end_port = start_port + max_port
for port in range(start_port, end_port): allocated_port = DynamipsHypervisor.find_unused_port(start_port, end_port, self._host, socket_type="UDP")
if port > end_port: if allocated_port - self._current_udp_port > 1:
raise DynamipsError("Could not find a free port between {0} and {1}".format(start_port, max_port)) self._current_udp_port += allocated_port - self._current_udp_port
try:
if self.host.__contains__(':'):
# IPv6 address support
s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
else: 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 self._current_udp_port += 1
return port return allocated_port
except socket.error as e:
if e.errno == errno.EADDRINUSE: # socket already in use
continue
else:
raise DynamipsError("UDP port allocation: {}".format(e))
def send_raw(self, string): def send_raw(self, string):
""" """

View File

@ -22,11 +22,12 @@ Represents a Dynamips hypervisor and starts/stops the associated Dynamips proces
import os import os
import time import time
import subprocess import subprocess
import logging
from .dynamips_hypervisor import DynamipsHypervisor from .dynamips_hypervisor import DynamipsHypervisor
from .dynamips_error import DynamipsError
logger = logging.getLogger(__name__) import logging
log = logging.getLogger(__name__)
class Hypervisor(DynamipsHypervisor): class Hypervisor(DynamipsHypervisor):
@ -34,26 +35,25 @@ class Hypervisor(DynamipsHypervisor):
Hypervisor. Hypervisor.
:param path: path to Dynamips executable :param path: path to Dynamips executable
:param workingdir: working directory :param working_dir: working directory
:param port: port for this hypervisor :param port: port for this hypervisor
:param host: host/address for this hypervisor :param host: host/address for this hypervisor
""" """
_instance_count = 0 _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 # create an unique ID
self._id = Hypervisor._instance_count self._id = Hypervisor._instance_count
Hypervisor._instance_count += 1 Hypervisor._instance_count += 1
self._path = path self._path = path
self._workingdir = workingdir
self._command = [] self._command = []
self._process = None self._process = None
self._stdout = None self._stdout_file = ""
# settings used the load-balance hypervisors # settings used the load-balance hypervisors
# (for the hypervisor manager) # (for the hypervisor manager)
@ -130,26 +130,6 @@ class Hypervisor(DynamipsHypervisor):
self._host = host 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 @property
def image_ref(self): def image_ref(self):
""" """
@ -210,20 +190,18 @@ class Hypervisor(DynamipsHypervisor):
self._command = self._build_command() self._command = self._build_command()
try: try:
logger.info("Starting Dynamips: {}".format(self._command)) log.info("starting Dynamips: {}".format(self._command))
# TODO: create unique filename for stdout self._stdout_file = os.path.join(self._working_dir, "dynamips-{}.log".format(self._port))
self.stdout_file = os.path.join(self._workingdir, "dynamips.log") log.info("logging to {}".format(self._stdout_file))
fd = open(self.stdout_file, "w") with open(self._stdout_file, "w") as fd:
# TODO: check for exceptions and if process has already been started
self._process = subprocess.Popen(self._command, self._process = subprocess.Popen(self._command,
stdout=fd, stdout=fd,
stderr=subprocess.STDOUT, stderr=subprocess.STDOUT,
cwd=self._workingdir) cwd=self._working_dir)
logger.info("Dynamips started PID={}".format(self._process.pid)) log.info("Dynamips started PID={}".format(self._process.pid))
except OSError as e: except EnvironmentError as e:
logger.error("Could not start Dynamips: {}".format(e)) log.error("could not start Dynamips: {}".format(e))
finally: raise DynamipsError("could not start Dynamips: {}".format(e))
fd.close()
def stop(self): def stop(self):
""" """
@ -232,7 +210,7 @@ class Hypervisor(DynamipsHypervisor):
if self.is_running(): if self.is_running():
DynamipsHypervisor.stop(self) 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. # give some time for the hypervisor to properly stop.
# time to delete UNIX NIOs for instance. # time to delete UNIX NIOs for instance.
time.sleep(0.01) time.sleep(0.01)
@ -245,9 +223,13 @@ class Hypervisor(DynamipsHypervisor):
Only use when the process has been stopped or has crashed. Only use when the process has been stopped or has crashed.
""" """
# TODO: check for exceptions output = ""
with open(self.stdout_file) as file: if self._stdout_file:
try:
with open(self._stdout_file) as file:
output = file.read() output = file.read()
except EnvironmentError as e:
log.warn("could not read {}: {}".format(self._stdout_file, e))
return output return output
def is_running(self): def is_running(self):

View File

@ -19,8 +19,8 @@
Manages Dynamips hypervisors (load-balancing etc.) Manages Dynamips hypervisors (load-balancing etc.)
""" """
from __future__ import unicode_literals
from .hypervisor import Hypervisor from .hypervisor import Hypervisor
from .dynamips_error import DynamipsError
import socket import socket
import time import time
import logging import logging
@ -33,7 +33,7 @@ class HypervisorManager(object):
Manages Dynamips hypervisors. Manages Dynamips hypervisors.
:param path: path to the Dynamips executable :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 host: host/address for hypervisors to listen to
:param base_port: base TCP port for hypervisors :param base_port: base TCP port for hypervisors
:param base_console: base TCP port for consoles :param base_console: base TCP port for consoles
@ -43,7 +43,7 @@ class HypervisorManager(object):
def __init__(self, def __init__(self,
path, path,
workingdir, working_dir,
host='127.0.0.1', host='127.0.0.1',
base_hypervisor_port=7200, base_hypervisor_port=7200,
base_console_port=2000, base_console_port=2000,
@ -52,7 +52,7 @@ class HypervisorManager(object):
self._hypervisors = [] self._hypervisors = []
self._path = path self._path = path
self._workingdir = workingdir self._working_dir = working_dir
self._host = host self._host = host
self._base_hypervisor_port = base_hypervisor_port self._base_hypervisor_port = base_hypervisor_port
self._current_port = self._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)) log.info("Dynamips path set to {}".format(self._path))
@property @property
def workingdir(self): def working_dir(self):
""" """
Returns the Dynamips working directory path. Returns the Dynamips working directory path.
:returns: path to Dynamips working directory :returns: path to Dynamips working directory
""" """
return self._workingdir return self._working_dir
@workingdir.setter @working_dir.setter
def workingdir(self, workingdir): def working_dir(self, working_dir):
""" """
Sets a new path to the Dynamips working directory. 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 self._working_dir = working_dir
log.info("working directory set to {}".format(self._workingdir)) 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 @property
def base_hypervisor_port(self): def base_hypervisor_port(self):
@ -406,16 +410,12 @@ class HypervisorManager(object):
# try to connect for 10 seconds # try to connect for 10 seconds
while(time.time() - begin < 10.0): while(time.time() - begin < 10.0):
time.sleep(0.01) time.sleep(0.01)
sock = None
try: try:
sock = socket.create_connection((host, port), timeout) with socket.create_connection((host, port), timeout):
pass
except socket.error as e: except socket.error as e:
last_exception = e last_exception = e
#time.sleep(0.01)
continue continue
finally:
if sock:
sock.close()
connection_success = True connection_success = True
break break
@ -426,6 +426,25 @@ class HypervisorManager(object):
else: else:
log.info("Dynamips server ready after {:.4f} seconds".format(time.time() - begin)) 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): def start_new_hypervisor(self):
""" """
Creates a new Dynamips process and start it. Creates a new Dynamips process and start it.
@ -433,15 +452,23 @@ class HypervisorManager(object):
:returns: the new hypervisor instance :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, hypervisor = Hypervisor(self._path,
self._workingdir, self._working_dir,
self._host, self._host,
self._current_port) port)
log.info("creating new hypervisor {}:{}".format(hypervisor.host, hypervisor.port)) log.info("creating new hypervisor {}:{}".format(hypervisor.host, hypervisor.port))
hypervisor.start() 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)) log.info("hypervisor {}:{} has successfully started".format(hypervisor.host, hypervisor.port))
hypervisor.connect() hypervisor.connect()
@ -450,7 +477,6 @@ class HypervisorManager(object):
hypervisor.baseudp = self._current_base_udp_port hypervisor.baseudp = self._current_base_udp_port
self._current_base_udp_port += self._udp_incrementation_per_hypervisor self._current_base_udp_port += self._udp_incrementation_per_hypervisor
self._hypervisors.append(hypervisor) self._hypervisors.append(hypervisor)
self._current_port += 1
return hypervisor return hypervisor
def allocate_hypervisor_for_router(self, router_ios_image, router_ram): def allocate_hypervisor_for_router(self, router_ios_image, router_ram):

View File

@ -20,7 +20,6 @@ Interface for Dynamips virtual ATM bridge module ("atm_bridge").
http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L622 http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L622
""" """
from __future__ import unicode_literals
from ..dynamips_error import DynamipsError from ..dynamips_error import DynamipsError

View File

@ -20,7 +20,6 @@ Interface for Dynamips virtual ATM switch module ("atmsw").
http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L593 http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L593
""" """
from __future__ import unicode_literals
from ..dynamips_error import DynamipsError from ..dynamips_error import DynamipsError
import logging import logging

View File

@ -20,8 +20,6 @@ Interface for Dynamips NIO bridge module ("nio_bridge").
http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L538 http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L538
""" """
from __future__ import unicode_literals
class Bridge(object): class Bridge(object):
""" """
@ -36,6 +34,7 @@ class Bridge(object):
def __init__(self, hypervisor, name): def __init__(self, hypervisor, name):
self._hypervisor = hypervisor self._hypervisor = hypervisor
self._allocated_names.append(name)
self._name = '"' + name + '"' # put name into quotes to protect spaces self._name = '"' + name + '"' # put name into quotes to protect spaces
self._hypervisor.send("nio_bridge create {}".format(self._name)) self._hypervisor.send("nio_bridge create {}".format(self._name))
self._hypervisor.devices.append(self) self._hypervisor.devices.append(self)

View File

@ -20,7 +20,6 @@ Interface for Dynamips virtual Cisco 1700 instances module ("c1700")
http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L428 http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L428
""" """
from __future__ import unicode_literals
from .router import Router from .router import Router
from ..adapters.c1700_mb_1fe import C1700_MB_1FE from ..adapters.c1700_mb_1fe import C1700_MB_1FE
from ..adapters.c1700_mb_wic1 import C1700_MB_WIC1 from ..adapters.c1700_mb_wic1 import C1700_MB_WIC1

View File

@ -20,7 +20,6 @@ Interface for Dynamips virtual Cisco 2600 instances module ("c2600")
http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L404 http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L404
""" """
from __future__ import unicode_literals
from .router import Router from .router import Router
from ..adapters.c2600_mb_1e import C2600_MB_1E from ..adapters.c2600_mb_1e import C2600_MB_1E
from ..adapters.c2600_mb_2e import C2600_MB_2E from ..adapters.c2600_mb_2e import C2600_MB_2E

View File

@ -20,7 +20,6 @@ Interface for Dynamips virtual Cisco 2691 instances module ("c2691")
http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L387 http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L387
""" """
from __future__ import unicode_literals
from .router import Router from .router import Router
from ..adapters.gt96100_fe import GT96100_FE from ..adapters.gt96100_fe import GT96100_FE

View File

@ -20,7 +20,6 @@ Interface for Dynamips virtual Cisco 3600 instances module ("c3600")
http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L366 http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L366
""" """
from __future__ import unicode_literals
from .router import Router from .router import Router
from ..adapters.leopard_2fe import Leopard_2FE from ..adapters.leopard_2fe import Leopard_2FE

View File

@ -20,7 +20,6 @@ Interface for Dynamips virtual Cisco 3725 instances module ("c3725")
http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L346 http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L346
""" """
from __future__ import unicode_literals
from .router import Router from .router import Router
from ..adapters.gt96100_fe import GT96100_FE from ..adapters.gt96100_fe import GT96100_FE

View File

@ -20,7 +20,6 @@ Interface for Dynamips virtual Cisco 3745 instances module ("c3745")
http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L326 http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L326
""" """
from __future__ import unicode_literals
from .router import Router from .router import Router
from ..adapters.gt96100_fe import GT96100_FE from ..adapters.gt96100_fe import GT96100_FE

View File

@ -20,7 +20,6 @@ Interface for Dynamips virtual Cisco 7200 instances module ("c7200")
http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L294 http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L294
""" """
from __future__ import unicode_literals
from ..dynamips_error import DynamipsError from ..dynamips_error import DynamipsError
from .router import Router from .router import Router
from ..adapters.c7200_io_2fe import C7200_IO_2FE from ..adapters.c7200_io_2fe import C7200_IO_2FE

View File

@ -20,8 +20,6 @@ Interface for Dynamips virtual Ethernet switch module ("ethsw").
http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L558 http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L558
""" """
from __future__ import unicode_literals
from ..dynamips_error import DynamipsError from ..dynamips_error import DynamipsError
import logging import logging

View File

@ -20,7 +20,6 @@ Interface for Dynamips virtual Frame-Relay switch module.
http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L642 http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L642
""" """
from __future__ import unicode_literals
from ..dynamips_error import DynamipsError from ..dynamips_error import DynamipsError
import logging import logging

View File

@ -19,7 +19,6 @@
Hub object that uses the Bridge interface to create a hub with ports. Hub object that uses the Bridge interface to create a hub with ports.
""" """
from __future__ import unicode_literals
from .bridge import Bridge from .bridge import Bridge
from ..dynamips_error import DynamipsError from ..dynamips_error import DynamipsError
@ -53,7 +52,6 @@ class Hub(Bridge):
break break
name_id += 1 name_id += 1
self._allocated_names.append(name)
self._mapping = {} self._mapping = {}
Bridge.__init__(self, hypervisor, name) Bridge.__init__(self, hypervisor, name)

View File

@ -20,7 +20,7 @@ Interface for Dynamips virtual Machine module ("vm")
http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L77 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 from ..dynamips_error import DynamipsError
import time import time
import sys import sys
@ -68,6 +68,8 @@ class Router(object):
self._name = '"' + name + '"' # put name into quotes to protect spaces self._name = '"' + name + '"' # put name into quotes to protect spaces
self._platform = platform self._platform = platform
self._image = "" self._image = ""
self._startup_config = ""
self._private_config = ""
self._ram = 128 # Megabytes self._ram = 128 # Megabytes
self._nvram = 128 # Kilobytes self._nvram = 128 # Kilobytes
self._mmap = True self._mmap = True
@ -100,8 +102,12 @@ class Router(object):
log.info("router {platform} {name} [id={id}] has been created".format(name=self._name, log.info("router {platform} {name} [id={id}] has been created".format(name=self._name,
platform=platform, platform=platform,
id=self._id)) 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 # get the default base MAC address
self._mac_addr = self._hypervisor.send("{platform} get_mac_addr {name}".format(platform=self._platform, 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, router_defaults = {"platform": self._platform,
"image": self._image, "image": self._image,
"startup_config": self._startup_config,
"private_config": self._private_config,
"ram": self._ram, "ram": self._ram,
"nvram": self._nvram, "nvram": self._nvram,
"mmap": self._mmap, "mmap": self._mmap,
@ -249,7 +257,7 @@ class Router(object):
Deletes this router. 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) self._hypervisor.devices.remove(self)
log.info("router {name} [id={id}] has been deleted".format(name=self._name, id=self._id)) 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 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=''): def set_config(self, startup_config, private_config=''):
""" """
Sets the config files that are pushed to startup-config and Sets the config files that are pushed to startup-config and
@ -394,6 +442,8 @@ class Router(object):
(keep existing data when if an empty string) (keep existing data when if an empty string)
""" """
if self._startup_config != startup_config or self._private_config != private_config:
self._hypervisor.send("vm set_config {name} {startup} {private}".format(name=self._name, self._hypervisor.send("vm set_config {name} {startup} {private}".format(name=self._name,
startup='"' + startup_config + '"', startup='"' + startup_config + '"',
private='"' + private_config + '"')) private='"' + private_config + '"'))

View File

@ -49,9 +49,16 @@ class Server(object):
self._host = host self._host = host
self._port = port self._port = port
if ipc: 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: 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._ipc = ipc
self._modules = [] self._modules = []

Binary file not shown.

View File

@ -18,9 +18,9 @@ def test_host(hypervisor):
assert hypervisor.host == "127.0.0.1" 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): def test_path(hypervisor):

View File

@ -1,5 +1,5 @@
[tox] [tox]
envlist = py27, py33 envlist = py33
[testenv] [testenv]
commands = py.test [] -s tests commands = py.test [] -s tests