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()))
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()

View File

@ -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):
"""

View File

@ -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):
"""

View File

@ -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):
"""

View File

@ -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):
"""

View File

@ -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):

View File

@ -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):

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
"""
from __future__ import unicode_literals
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
"""
from __future__ import unicode_literals
from ..dynamips_error import DynamipsError
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
"""
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)

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
"""
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

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
"""
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

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
"""
from __future__ import unicode_literals
from .router import Router
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
"""
from __future__ import unicode_literals
from .router import Router
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
"""
from __future__ import unicode_literals
from .router import Router
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
"""
from __future__ import unicode_literals
from .router import Router
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
"""
from __future__ import unicode_literals
from ..dynamips_error import DynamipsError
from .router import Router
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
"""
from __future__ import unicode_literals
from ..dynamips_error import DynamipsError
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
"""
from __future__ import unicode_literals
from ..dynamips_error import DynamipsError
import logging

View File

@ -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)

View File

@ -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):
"""

View File

@ -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.

View File

@ -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):

View File

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