diff --git a/README.rst b/README.rst index 97f6078f..34fb8619 100644 --- a/README.rst +++ b/README.rst @@ -2,4 +2,4 @@ GNS3-server =========== GNS3 server manages emulators such as Dynamips, VirtualBox or Qemu/KVM. -Clients like the GNS3 GUI controls the server using an API over Websockets. +Clients like the GNS3 GUI controls the server using a JSON-RPC API over Websockets. diff --git a/gns3server/handlers/jsonrpc_websocket.py b/gns3server/handlers/jsonrpc_websocket.py index 59aa6352..a22328bb 100644 --- a/gns3server/handlers/jsonrpc_websocket.py +++ b/gns3server/handlers/jsonrpc_websocket.py @@ -23,7 +23,7 @@ import zmq import uuid import tornado.websocket from tornado.escape import json_decode -from ..jsonrpc import JSONRPCParseError, JSONRPCInvalidRequest, JSONRPCMethodNotFound +from ..jsonrpc import JSONRPCParseError, JSONRPCInvalidRequest, JSONRPCMethodNotFound, JSONRPCNotification import logging log = logging.getLogger(__name__) @@ -59,7 +59,7 @@ class JSONRPCWebSocket(tornado.websocket.WebSocketHandler): return self._session_id @classmethod - def dispatch_message(cls, message): + def dispatch_message(cls, stream, message): """ Sends a message to Websocket client @@ -71,7 +71,13 @@ class JSONRPCWebSocket(tornado.websocket.WebSocketHandler): # ZMQ responses are encoded in JSON # format is a JSON array: [session ID, JSON-RPC response] - json_message = json_decode(message[1]) + try: + json_message = json_decode(message[1]) + except ValueError as e: + stream.send_string("Cannot decode message!") + log.critical("Couldn't decode message: {}".format(e)) + return + session_id = json_message[0] jsonrpc_response = json_message[1] @@ -94,8 +100,8 @@ class JSONRPCWebSocket(tornado.websocket.WebSocketHandler): # Make sure the destination is not already registered # by another module for instance assert destination not in cls.destinations - log.info("registering {} as a destination for {}".format(destination, - module)) + log.debug("registering {} as a destination for the {} module".format(destination, + module)) cls.destinations[destination] = module def open(self): @@ -119,8 +125,8 @@ class JSONRPCWebSocket(tornado.websocket.WebSocketHandler): request = json_decode(message) jsonrpc_version = request["jsonrpc"] method = request["method"] - # warning: notifications cannot be sent by a client because check for an "id" here - request_id = request["id"] + # This is a JSON-RPC notification if request_id is None + request_id = request.get("id") except: return self.write_message(JSONRPCParseError()()) @@ -128,7 +134,11 @@ class JSONRPCWebSocket(tornado.websocket.WebSocketHandler): return self.write_message(JSONRPCInvalidRequest()()) if method not in self.destinations: - return self.write_message(JSONRPCMethodNotFound(request_id)()) + if request_id: + return self.write_message(JSONRPCMethodNotFound(request_id)()) + else: + # This is a notification, silently ignore this error... + return module = self.destinations[method] # ZMQ requests are encoded in JSON @@ -136,7 +146,7 @@ class JSONRPCWebSocket(tornado.websocket.WebSocketHandler): zmq_request = [self.session_id, request] # Route to the correct module self.zmq_router.send_string(module, zmq.SNDMORE) - # Send the encoded JSON request + # Send the JSON request self.zmq_router.send_json(zmq_request) def on_close(self): @@ -146,3 +156,14 @@ class JSONRPCWebSocket(tornado.websocket.WebSocketHandler): log.info("Websocket client {} disconnected".format(self.session_id)) self.clients.remove(self) + + # Reset the modules if there are no clients anymore + # Modules must implement a reset destination + if not self.clients: + for destination, module in self.destinations.items(): + if destination.endswith("reset"): + # Route to the correct module + self.zmq_router.send_string(module, zmq.SNDMORE) + # Send the JSON request + notification = JSONRPCNotification(destination)() + self.zmq_router.send_json([self.session_id, notification]) diff --git a/gns3server/jsonrpc.py b/gns3server/jsonrpc.py index 21396b9d..ce9813b8 100644 --- a/gns3server/jsonrpc.py +++ b/gns3server/jsonrpc.py @@ -159,8 +159,10 @@ class JSONRPCRequest(JSONRPCObject): :param request_id: JSON-RPC identifier (generated by default) """ - def __init__(self, method, params=None, request_id=str(uuid.uuid1())): + def __init__(self, method, params=None, request_id=None): JSONRPCObject.__init__(self) + if request_id == None: + request_id = str(uuid.uuid4()) self.id = request_id self.method = method if params: diff --git a/gns3server/main.py b/gns3server/main.py index 55b9ee3a..9530bd07 100644 --- a/gns3server/main.py +++ b/gns3server/main.py @@ -24,7 +24,7 @@ import gns3server # command line options from tornado.options import define -define("host", default="127.0.0.1", help="run on the given host/IP address", type=str) +define("host", default="0.0.0.0", help="run on the given host/IP address", type=str) define("port", default=8000, help="run on the given port", type=int) define("ipc", default=False, help="use IPC for module communication", type=bool) diff --git a/gns3server/module_manager.py b/gns3server/module_manager.py index 14bbd440..6c7b28a4 100644 --- a/gns3server/module_manager.py +++ b/gns3server/module_manager.py @@ -79,7 +79,7 @@ class ModuleManager(object): if issubclass(module_class[1], IModule): # make sure the module class has IModule as a parent if module_class[1].__module__ == name: - log.info("found and 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]) self._modules.append(info) except: diff --git a/gns3server/modules/base.py b/gns3server/modules/base.py index b12b7a5f..ff80143d 100644 --- a/gns3server/modules/base.py +++ b/gns3server/modules/base.py @@ -92,6 +92,10 @@ 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 run(self): """ Starts the event loop @@ -158,9 +162,9 @@ class IModule(multiprocessing.Process): # add session to the response response = [self._current_session, jsonrpc_response] - log.info("ZeroMQ client ({}) sending JSON-RPC custom error {} for call id {}".format(self.name, - message, - self._current_call_id)) + log.info("ZeroMQ client ({}) sending JSON-RPC custom error: {} for call id {}".format(self.name, + message, + self._current_call_id)) self._stream.send_json(response) def _decode_request(self, request): @@ -188,7 +192,12 @@ class IModule(multiprocessing.Process): return log.debug("Routing request to {}: {}".format(destination, request[1])) - self.destination[destination](self, params) + + try: + self.destination[destination](self, params) + except Exception as e: + log.error("uncaught exception {type}".format(type=type(e)), exc_info=1) + self.send_custom_error("uncaught exception {type}: {string}".format(type=type(e), string=str(e))) def destinations(self): """ diff --git a/gns3server/modules/dynamips/__init__.py b/gns3server/modules/dynamips/__init__.py index 5ae34ad3..72027af0 100644 --- a/gns3server/modules/dynamips/__init__.py +++ b/gns3server/modules/dynamips/__init__.py @@ -15,13 +15,18 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +Dynamips server module. +""" + from gns3server.modules import IModule import gns3server.jsonrpc as jsonrpc + from .hypervisor import Hypervisor from .hypervisor_manager import HypervisorManager from .dynamips_error import DynamipsError -# nodes +# Nodes from .nodes.router import Router from .nodes.c1700 import C1700 from .nodes.c2600 import C2600 @@ -37,7 +42,7 @@ from .nodes.atm_bridge import ATMBridge from .nodes.frame_relay_switch import FrameRelaySwitch from .nodes.hub import Hub -# adapters +# Adapters from .adapters.c7200_io_2fe import C7200_IO_2FE from .adapters.c7200_io_fe import C7200_IO_FE from .adapters.c7200_io_ge_e import C7200_IO_GE_E @@ -71,35 +76,188 @@ from .nios.nio_fifo import NIO_FIFO from .nios.nio_mcast import NIO_Mcast from .nios.nio_null import NIO_Null +from .backends import vm +from .backends import ethsw +from .backends import ethhub +from .backends import frsw +from .backends import atmsw import logging log = logging.getLogger(__name__) class Dynamips(IModule): + """ + Dynamips module. + + :param name: module name + :param args: arguments for the module + :param kwargs: named arguments for the module + """ def __init__(self, name=None, args=(), kwargs={}): IModule.__init__(self, name=name, args=args, kwargs=kwargs) - # start the hypervisor manager - #self._hypervisor_manager = HypervisorManager("/usr/bin/dynamips", "/tmp") + self._hypervisor_manager = None + self._routers = {} + self._ethernet_switches = {} + self._frame_relay_switches = {} + self._atm_switches = {} + self._ethernet_hubs = {} + +# def __del__(self): +# +# self._hypervisor_manager.stop_all_hypervisors() + + @IModule.route("dynamips.reset") + def reset(self, request): + """ + Resets the module. + + :param request: JSON request + """ + + # stop all Dynamips hypervisors + self._hypervisor_manager.stop_all_hypervisors() + + # resets the instance counters + Router.reset() + EthernetSwitch.reset() + Hub.reset() + FrameRelaySwitch.reset() + ATMSwitch.reset() + NIO_UDP.reset() + NIO_UDP_auto.reset() + NIO_UNIX.reset() + NIO_VDE.reset() + NIO_TAP.reset() + NIO_GenericEthernet.reset() + NIO_LinuxEthernet.reset() + NIO_FIFO.reset() + NIO_Mcast.reset() + NIO_Null.reset() + + self._routers.clear() + self._ethernet_switches.clear() + self._frame_relay_switches.clear() + self._atm_switches.clear() + + log.info("dynamips module has been reset") + + @IModule.route("dynamips.settings") + def settings(self, request): + """ + Set or update settings. + + :param request: JSON request + """ + + print("Create") + if not self._hypervisor_manager: + self._hypervisor_manager = HypervisorManager(request["path"], "/tmp") + + for name, value in request.items(): + if hasattr(self._hypervisor_manager, name) and getattr(self._hypervisor_manager, name) != value: + setattr(self._hypervisor_manager, name, value) @IModule.route("dynamips.echo") def echo(self, request): + """ + Echo end point for testing purposes. + + :param request: JSON request + """ + if request == None: self.send_param_error() - return - log.debug("received request {}".format(request)) - self.send_response(request) - - @IModule.route("dynamips.create_vm") - def create_vm(self, request): - print("Create VM!") - log.debug("received request {}".format(request)) - self.send_response(request) - - @IModule.route("dynamips.start_vm") - def start_vm(self, request): - print("Start VM!") - log.debug("received request {}".format(request)) - self.send_response(request) + else: + log.debug("received request {}".format(request)) + self.send_response(request) + + def create_nio(self, node, request): + + nio = None + if request["nio"] == "NIO_UDP": + lport = request["lport"] + rhost = request["rhost"] + rport = request["rport"] + nio = NIO_UDP(node.hypervisor, lport, rhost, rport) + elif request["nio"] == "NIO_GenericEthernet": + ethernet_device = request["ethernet_device"] + nio = NIO_GenericEthernet(node.hypervisor, ethernet_device) + elif request["nio"] == "NIO_LinuxEthernet": + ethernet_device = request["ethernet_device"] + nio = NIO_LinuxEthernet(node.hypervisor, ethernet_device) + elif request["nio"] == "NIO_TAP": + tap_device = request["tap_device"] + nio = NIO_TAP(node.hypervisor, tap_device) + elif request["nio"] == "NIO_UNIX": + local_file = request["local_file"] + remote_file = request["remote_file"] + nio = NIO_UNIX(node.hypervisor, local_file, remote_file) + elif request["nio"] == "NIO_VDE": + control_file = request["control_file"] + local_file = request["local_file"] + nio = NIO_VDE(node.hypervisor, control_file, local_file) + elif request["nio"] == "NIO_Null": + nio = NIO_Null(node.hypervisor) + return nio + + def allocate_udp_port(self, node): + """ + Allocates a UDP port in order to create an UDP NIO. + + :param node: the node that needs to allocate an UDP port + """ + + port = node.hypervisor.allocate_udp_port() + host = node.hypervisor.host + + log.info("{} [id={}] has allocated UDP port {} with host {}".format(node.name, + node.id, + port, + host)) + response = {"lport": port, + "lhost": host} + + return response + + def set_ghost_ios(self, router): + + if not router.mmap: + raise DynamipsError("mmap support is required to enable ghost IOS support") + + ghost_instance = router.formatted_ghost_file() + all_ghosts = [] + + # search of an existing ghost instance across all hypervisors + for hypervisor in self._hypervisor_manager.hypervisors: + all_ghosts.extend(hypervisor.ghosts) + + if ghost_instance not in all_ghosts: + # create a new ghost IOS instance + ghost = Router(router.hypervisor, "ghost-" + ghost_instance, router.platform, ghost_flag=True) + ghost.image = router.image + # for 7200s, the NPE must be set when using an NPE-G2. + if router.platform == "c7200": + ghost.npe = router.npe + ghost.ghost_status = 1 + ghost.ghost_file = ghost_instance + ghost.ram = router.ram + ghost.start() + ghost.stop() + ghost.delete() + + router.ghost_status = 2 + router.ghost_file = ghost_instance + + @IModule.route("dynamips.nio.get_interfaces") + def nio_get_interfaces(self, request): + """ + Get all the network interfaces on this host. + + :param request: JSON request + """ + + import netifaces + self.send_response(netifaces.interfaces()) diff --git a/gns3server/modules/dynamips/adapters/adapter.py b/gns3server/modules/dynamips/adapters/adapter.py index 4e10bf99..f54f26f9 100644 --- a/gns3server/modules/dynamips/adapters/adapter.py +++ b/gns3server/modules/dynamips/adapters/adapter.py @@ -124,6 +124,17 @@ class Adapter(object): self._ports[port_id] = None + def get_nio(self, port_id): + """ + Returns the NIO assigned to a port. + + :params port_id: port ID (integer) + + :returns: NIO object + """ + + return self._ports[port_id] + @property def ports(self): """ diff --git a/gns3server/modules/dynamips/adapters/c1700_mb_1fe.py b/gns3server/modules/dynamips/adapters/c1700_mb_1fe.py index 71f992e7..3c67f3df 100644 --- a/gns3server/modules/dynamips/adapters/c1700_mb_1fe.py +++ b/gns3server/modules/dynamips/adapters/c1700_mb_1fe.py @@ -33,11 +33,3 @@ class C1700_MB_1FE(Adapter): def removable(self): return False - - def interface_type(self): - - return "FastEthernet" - - def medium(self): - - return "Ethernet" diff --git a/gns3server/modules/dynamips/adapters/c1700_mb_wic1.py b/gns3server/modules/dynamips/adapters/c1700_mb_wic1.py index 7705984c..eca72358 100644 --- a/gns3server/modules/dynamips/adapters/c1700_mb_wic1.py +++ b/gns3server/modules/dynamips/adapters/c1700_mb_wic1.py @@ -29,16 +29,8 @@ class C1700_MB_WIC1(Adapter): def __str__(self): - return "C1700_MB_WIC1" + return "C1700-MB-WIC1" def removable(self): return False - - def interface_type(self): - - return "N/A" - - def medium(self): - - return "N/A" diff --git a/gns3server/modules/dynamips/adapters/c2600_mb_1e.py b/gns3server/modules/dynamips/adapters/c2600_mb_1e.py index 2ccb1661..26fe5497 100644 --- a/gns3server/modules/dynamips/adapters/c2600_mb_1e.py +++ b/gns3server/modules/dynamips/adapters/c2600_mb_1e.py @@ -33,11 +33,3 @@ class C2600_MB_1E(Adapter): def removable(self): return False - - def interface_type(self): - - return "Ethernet" - - def medium(self): - - return "Ethernet" diff --git a/gns3server/modules/dynamips/adapters/c2600_mb_1fe.py b/gns3server/modules/dynamips/adapters/c2600_mb_1fe.py index a630c194..768d9c95 100644 --- a/gns3server/modules/dynamips/adapters/c2600_mb_1fe.py +++ b/gns3server/modules/dynamips/adapters/c2600_mb_1fe.py @@ -34,11 +34,3 @@ class C2600_MB_1FE(Adapter): def removable(self): return False - - def interface_type(self): - - return "FastEthernet" - - def medium(self): - - return "Ethernet" diff --git a/gns3server/modules/dynamips/adapters/c2600_mb_2e.py b/gns3server/modules/dynamips/adapters/c2600_mb_2e.py index 68ea2db8..c2ca7442 100644 --- a/gns3server/modules/dynamips/adapters/c2600_mb_2e.py +++ b/gns3server/modules/dynamips/adapters/c2600_mb_2e.py @@ -33,11 +33,3 @@ class C2600_MB_2E(Adapter): def removable(self): return False - - def interface_type(self): - - return "Ethernet" - - def medium(self): - - return "Ethernet" diff --git a/gns3server/modules/dynamips/adapters/c2600_mb_2fe.py b/gns3server/modules/dynamips/adapters/c2600_mb_2fe.py index 3a2d59be..a7e6df14 100644 --- a/gns3server/modules/dynamips/adapters/c2600_mb_2fe.py +++ b/gns3server/modules/dynamips/adapters/c2600_mb_2fe.py @@ -33,11 +33,3 @@ class C2600_MB_2FE(Adapter): def removable(self): return False - - def interface_type(self): - - return "FastEthernet" - - def medium(self): - - return "Ethernet" diff --git a/gns3server/modules/dynamips/adapters/c7200_io_2fe.py b/gns3server/modules/dynamips/adapters/c7200_io_2fe.py index 0ee8674f..0b8ae8a4 100644 --- a/gns3server/modules/dynamips/adapters/c7200_io_2fe.py +++ b/gns3server/modules/dynamips/adapters/c7200_io_2fe.py @@ -28,16 +28,8 @@ class C7200_IO_2FE(Adapter): def __str__(self): - return "C7200_IO_2FE" + return "C7200-IO-2FE" def removable(self): return False - - def interface_type(self): - - return "FastEthernet" - - def medium(self): - - return "Ethernet" diff --git a/gns3server/modules/dynamips/adapters/c7200_io_fe.py b/gns3server/modules/dynamips/adapters/c7200_io_fe.py index 22200ded..56e86cf1 100644 --- a/gns3server/modules/dynamips/adapters/c7200_io_fe.py +++ b/gns3server/modules/dynamips/adapters/c7200_io_fe.py @@ -28,16 +28,8 @@ class C7200_IO_FE(Adapter): def __str__(self): - return "C7200_IO_FE" + return "C7200-IO-FE" def removable(self): return False - - def interface_type(self): - - return "FastEthernet" - - def medium(self): - - return "Ethernet" diff --git a/gns3server/modules/dynamips/adapters/c7200_io_ge_e.py b/gns3server/modules/dynamips/adapters/c7200_io_ge_e.py index dd732dfa..12ebaed6 100644 --- a/gns3server/modules/dynamips/adapters/c7200_io_ge_e.py +++ b/gns3server/modules/dynamips/adapters/c7200_io_ge_e.py @@ -33,11 +33,3 @@ class C7200_IO_GE_E(Adapter): def removable(self): return False - - def interface_type(self): - - return "GigabitEthernet" - - def medium(self): - - return "Ethernet" diff --git a/gns3server/modules/dynamips/adapters/gt96100_fe.py b/gns3server/modules/dynamips/adapters/gt96100_fe.py index 27217e31..35af37aa 100644 --- a/gns3server/modules/dynamips/adapters/gt96100_fe.py +++ b/gns3server/modules/dynamips/adapters/gt96100_fe.py @@ -30,11 +30,3 @@ class GT96100_FE(Adapter): def removable(self): return False - - def interface_type(self): - - return "FastEthernet" - - def medium(self): - - return "Ethernet" diff --git a/gns3server/modules/dynamips/adapters/leopard_2fe.py b/gns3server/modules/dynamips/adapters/leopard_2fe.py index c25744eb..0afa95c0 100644 --- a/gns3server/modules/dynamips/adapters/leopard_2fe.py +++ b/gns3server/modules/dynamips/adapters/leopard_2fe.py @@ -34,11 +34,3 @@ class Leopard_2FE(Adapter): def removable(self): return False - - def interface_type(self): - - return "FastEthernet" - - def medium(self): - - return "Ethernet" diff --git a/gns3server/modules/dynamips/adapters/nm_16esw.py b/gns3server/modules/dynamips/adapters/nm_16esw.py index ea7bccce..fc3755cd 100644 --- a/gns3server/modules/dynamips/adapters/nm_16esw.py +++ b/gns3server/modules/dynamips/adapters/nm_16esw.py @@ -29,11 +29,3 @@ class NM_16ESW(Adapter): def __str__(self): return "NM-16ESW" - - def interface_type(self): - - return "FastEthernet" - - def medium(self): - - return "Ethernet" diff --git a/gns3server/modules/dynamips/adapters/nm_1e.py b/gns3server/modules/dynamips/adapters/nm_1e.py index 7663f934..ac200247 100644 --- a/gns3server/modules/dynamips/adapters/nm_1e.py +++ b/gns3server/modules/dynamips/adapters/nm_1e.py @@ -29,11 +29,3 @@ class NM_1E(Adapter): def __str__(self): return "NM-1E" - - def interface_type(self): - - return "Ethernet" - - def medium(self): - - return "Ethernet" diff --git a/gns3server/modules/dynamips/adapters/nm_1fe_tx.py b/gns3server/modules/dynamips/adapters/nm_1fe_tx.py index 02f19248..9723f703 100644 --- a/gns3server/modules/dynamips/adapters/nm_1fe_tx.py +++ b/gns3server/modules/dynamips/adapters/nm_1fe_tx.py @@ -29,11 +29,3 @@ class NM_1FE_TX(Adapter): def __str__(self): return "NM-1FE-TX" - - def interface_type(self): - - return "FastEthernet" - - def medium(self): - - return "Ethernet" diff --git a/gns3server/modules/dynamips/adapters/nm_4e.py b/gns3server/modules/dynamips/adapters/nm_4e.py index 32219522..ae6a51ed 100644 --- a/gns3server/modules/dynamips/adapters/nm_4e.py +++ b/gns3server/modules/dynamips/adapters/nm_4e.py @@ -29,11 +29,3 @@ class NM_4E(Adapter): def __str__(self): return "NM-4E" - - def interface_type(self): - - return "Ethernet" - - def medium(self): - - return "Ethernet" diff --git a/gns3server/modules/dynamips/adapters/nm_4t.py b/gns3server/modules/dynamips/adapters/nm_4t.py index 1021dc51..df6db299 100644 --- a/gns3server/modules/dynamips/adapters/nm_4t.py +++ b/gns3server/modules/dynamips/adapters/nm_4t.py @@ -29,11 +29,3 @@ class NM_4T(Adapter): def __str__(self): return "NM-4T" - - def interface_type(self): - - return "Serial" - - def medium(self): - - return "Serial" diff --git a/gns3server/modules/dynamips/adapters/pa_2fe_tx.py b/gns3server/modules/dynamips/adapters/pa_2fe_tx.py index 2706f51d..8589ff2e 100644 --- a/gns3server/modules/dynamips/adapters/pa_2fe_tx.py +++ b/gns3server/modules/dynamips/adapters/pa_2fe_tx.py @@ -29,11 +29,3 @@ class PA_2FE_TX(Adapter): def __str__(self): return "PA-2FE-TX" - - def interface_type(self): - - return "FastEthernet" - - def medium(self): - - return "Ethernet" diff --git a/gns3server/modules/dynamips/adapters/pa_4e.py b/gns3server/modules/dynamips/adapters/pa_4e.py index 44f5437d..32564992 100644 --- a/gns3server/modules/dynamips/adapters/pa_4e.py +++ b/gns3server/modules/dynamips/adapters/pa_4e.py @@ -29,11 +29,3 @@ class PA_4E(Adapter): def __str__(self): return "PA-4E" - - def interface_type(self): - - return "Ethernet" - - def medium(self): - - return "Ethernet" diff --git a/gns3server/modules/dynamips/adapters/pa_4t.py b/gns3server/modules/dynamips/adapters/pa_4t.py index 592eb688..6a098a24 100644 --- a/gns3server/modules/dynamips/adapters/pa_4t.py +++ b/gns3server/modules/dynamips/adapters/pa_4t.py @@ -29,11 +29,3 @@ class PA_4T(Adapter): def __str__(self): return "PA-4T+" - - def interface_type(self): - - return "Serial" - - def medium(self): - - return "Serial" diff --git a/gns3server/modules/dynamips/adapters/pa_8e.py b/gns3server/modules/dynamips/adapters/pa_8e.py index 4e5b5a7f..a6b5075f 100644 --- a/gns3server/modules/dynamips/adapters/pa_8e.py +++ b/gns3server/modules/dynamips/adapters/pa_8e.py @@ -29,11 +29,3 @@ class PA_8E(Adapter): def __str__(self): return "PA-8E" - - def interface_type(self): - - return "Ethernet" - - def medium(self): - - return "Ethernet" diff --git a/gns3server/modules/dynamips/adapters/pa_8t.py b/gns3server/modules/dynamips/adapters/pa_8t.py index c4851154..600a5c29 100644 --- a/gns3server/modules/dynamips/adapters/pa_8t.py +++ b/gns3server/modules/dynamips/adapters/pa_8t.py @@ -29,11 +29,3 @@ class PA_8T(Adapter): def __str__(self): return "PA-8T" - - def interface_type(self): - - return "Serial" - - def medium(self): - - return "Serial" diff --git a/gns3server/modules/dynamips/adapters/pa_a1.py b/gns3server/modules/dynamips/adapters/pa_a1.py index 72cb455e..21d51f15 100644 --- a/gns3server/modules/dynamips/adapters/pa_a1.py +++ b/gns3server/modules/dynamips/adapters/pa_a1.py @@ -29,11 +29,3 @@ class PA_A1(Adapter): def __str__(self): return "PA-A1" - - def interface_type(self): - - return "ATM" - - def medium(self): - - return "Serial" diff --git a/gns3server/modules/dynamips/adapters/pa_fe_tx.py b/gns3server/modules/dynamips/adapters/pa_fe_tx.py index 7a22e2af..70ce8489 100644 --- a/gns3server/modules/dynamips/adapters/pa_fe_tx.py +++ b/gns3server/modules/dynamips/adapters/pa_fe_tx.py @@ -29,11 +29,3 @@ class PA_FE_TX(Adapter): def __str__(self): return "PA-FE-TX" - - def interface_type(self): - - return "FastEthernet" - - def medium(self): - - return "Ethernet" diff --git a/gns3server/modules/dynamips/adapters/pa_ge.py b/gns3server/modules/dynamips/adapters/pa_ge.py index 20d5994b..f0287408 100644 --- a/gns3server/modules/dynamips/adapters/pa_ge.py +++ b/gns3server/modules/dynamips/adapters/pa_ge.py @@ -29,11 +29,3 @@ class PA_GE(Adapter): def __str__(self): return "PA-GE" - - def interface_type(self): - - return "GigabitEthernet" - - def medium(self): - - return "Ethernet" diff --git a/gns3server/modules/dynamips/adapters/pa_pos_oc3.py b/gns3server/modules/dynamips/adapters/pa_pos_oc3.py index 064f63cf..b120de97 100644 --- a/gns3server/modules/dynamips/adapters/pa_pos_oc3.py +++ b/gns3server/modules/dynamips/adapters/pa_pos_oc3.py @@ -29,11 +29,3 @@ class PA_POS_OC3(Adapter): def __str__(self): return "PA-POS-OC3" - - def interface_type(self): - - return "POS" - - def medium(self): - - return "SONET" diff --git a/gns3server/modules/dynamips/adapters/wic_1enet.py b/gns3server/modules/dynamips/adapters/wic_1enet.py index 790cf618..dac79b6b 100644 --- a/gns3server/modules/dynamips/adapters/wic_1enet.py +++ b/gns3server/modules/dynamips/adapters/wic_1enet.py @@ -29,14 +29,6 @@ class WIC_1ENET(object): return "WIC-1ENET" - def interface_type(self): - - return "Ethernet" - - def medium(self): - - return "Ethernet" - @property def interfaces(self): """ diff --git a/gns3server/modules/dynamips/adapters/wic_1t.py b/gns3server/modules/dynamips/adapters/wic_1t.py index c6f30c26..0f7cb3ad 100644 --- a/gns3server/modules/dynamips/adapters/wic_1t.py +++ b/gns3server/modules/dynamips/adapters/wic_1t.py @@ -29,14 +29,6 @@ class WIC_1T(object): return "WIC-1T" - def interface_type(self): - - return "Serial" - - def medium(self): - - return "Serial" - @property def interfaces(self): """ diff --git a/gns3server/modules/dynamips/adapters/wic_2t.py b/gns3server/modules/dynamips/adapters/wic_2t.py index 8e73cb35..2bf2d565 100644 --- a/gns3server/modules/dynamips/adapters/wic_2t.py +++ b/gns3server/modules/dynamips/adapters/wic_2t.py @@ -29,14 +29,6 @@ class WIC_2T(object): return "WIC-2T" - def interface_type(self): - - return "Serial" - - def medium(self): - - return "Serial" - @property def interfaces(self): """ diff --git a/gns3server/modules/dynamips/backends/__init__.py b/gns3server/modules/dynamips/backends/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/gns3server/modules/dynamips/backends/atmsw.py b/gns3server/modules/dynamips/backends/atmsw.py new file mode 100644 index 00000000..4479c2c3 --- /dev/null +++ b/gns3server/modules/dynamips/backends/atmsw.py @@ -0,0 +1,201 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2014 GNS3 Technologies Inc. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import re +from gns3server.modules import IModule +from ..nodes.atm_switch import ATMSwitch +from ..dynamips_error import DynamipsError + +import logging +log = logging.getLogger(__name__) + + +class ATMSW(object): + + @IModule.route("dynamips.atmsw.create") + def atmsw_create(self, request): + """ + Creates a new ATM switch. + + :param request: JSON request + """ + + name = None + if request and "name" in request: + name = request["name"] + + try: + hypervisor = self._hypervisor_manager.allocate_hypervisor_for_switch() + atmsw = ATMSwitch(hypervisor, name) + except DynamipsError as e: + self.send_custom_error(str(e)) + return + + response = {"name": atmsw.name, + "id": atmsw.id} + + self._atm_switches[atmsw.id] = atmsw + self.send_response(response) + + @IModule.route("dynamips.atmsw.delete") + def atmsw_delete(self, request): + """ + Deletes a ATM switch. + + :param request: JSON request + """ + + #TODO: JSON schema validation for the request + log.debug("received request {}".format(request)) + atmsw_id = request["id"] + atmsw = self._atm_switches[atmsw_id] + try: + atmsw.delete() + except DynamipsError as e: + self.send_custom_error(str(e)) + return + self.send_response(request) + + @IModule.route("dynamips.atmsw.update") + def atmsw_update(self, request): + """ + Updates a ATM switch. + + :param request: JSON request + """ + + #TODO: JSON schema validation for the request + log.debug("received request {}".format(request)) + atmsw_id = request["id"] + atmsw = self._atm_switches[atmsw_id] + self.send_response(request) + + @IModule.route("dynamips.atmsw.allocate_udp_port") + def atmsw_allocate_udp_port(self, request): + """ + Allocates a UDP port in order to create an UDP NIO for an + ATM switch. + + :param request: JSON request + """ + + #TODO: JSON schema validation for the request + log.debug("received request {}".format(request)) + atmsw_id = request["id"] + atmsw = self._atm_switches[atmsw_id] + + try: + # allocate a new UDP port + response = self.allocate_udp_port(atmsw) + except DynamipsError as e: + self.send_custom_error(str(e)) + return + + self.send_response(response) + + @IModule.route("dynamips.atmsw.add_nio") + def atmsw_add_nio(self, request): + """ + Adds an NIO (Network Input/Output) for an ATM switch. + + :param request: JSON request + """ + + #TODO: JSON schema validation for the request + log.debug("received request {}".format(request)) + atmsw_id = request["id"] + atmsw = self._atm_switches[atmsw_id] + + port = request["port"] + mapping = request["mapping"] + + try: + nio = self.create_nio(atmsw, request) + except DynamipsError as e: + self.send_custom_error(str(e)) + return + + try: + atmsw.add_nio(nio, port) + pvc_entry = re.compile(r"""^([0-9]*):([0-9]*):([0-9]*)$""") + for source, destination in mapping.items(): + match_source_pvc = pvc_entry.search(source) + match_destination_pvc = pvc_entry.search(destination) + if match_source_pvc and match_destination_pvc: + # add the virtual channels mapped with this port/nio + source_port, source_vpi, source_vci = map(int, match_source_pvc.group(1, 2, 3)) + destination_port, destination_vpi, destination_vci = map(int, match_destination_pvc.group(1, 2, 3)) + if atmsw.has_port(destination_port): + if (source_port, source_vpi, source_vci) not in atmsw.mapping and \ + (destination_port, destination_vpi, destination_vci) not in atmsw.mapping: + atmsw.map_pvc(source_port, source_vpi, source_vci, destination_port, destination_vpi, destination_vci) + atmsw.map_pvc(destination_port, destination_vpi, destination_vci, source_port, source_vpi, source_vci) + else: + # add the virtual paths mapped with this port/nio + source_port, source_vpi = map(int, source.split(':')) + destination_port, destination_vpi = map(int, destination.split(':')) + if atmsw.has_port(destination_port): + if (source_port, source_vpi) not in atmsw.mapping and (destination_port, destination_vpi) not in atmsw.mapping: + atmsw.map_vp(source_port, source_vpi, destination_port, destination_vpi) + atmsw.map_vp(destination_port, destination_vpi, source_port, source_vpi) + + except DynamipsError as e: + self.send_custom_error(str(e)) + return + + # for now send back the original request + self.send_response(request) + + @IModule.route("dynamips.atmsw.delete_nio") + def atmsw_delete_nio(self, request): + """ + Deletes an NIO (Network Input/Output). + + :param request: JSON request + """ + + #TODO: JSON schema validation for the request + log.debug("received request {}".format(request)) + atmsw_id = request["id"] + atmsw = self._atm_switches[atmsw_id] + port = request["port"] + + try: + for source, destination in atmsw.mapping.copy().items(): + if len(source) == 3 and len(destination) == 3: + # remove the virtual channels mapped with this port/nio + source_port, source_vpi, source_vci = source + destination_port, destination_vpi, destination_vci = destination + if port == source_port: + atmsw.unmap_pvc(source_port, source_vpi, source_vci, destination_port, destination_vpi, destination_vci) + atmsw.unmap_pvc(destination_port, destination_vpi, destination_vci, source_port, source_vpi, source_vci) + else: + # remove the virtual paths mapped with this port/nio + source_port, source_vpi = source + destination_port, destination_vpi = destination + if port == source_port: + atmsw.unmap_vp(source_port, source_vpi, destination_port, destination_vpi) + atmsw.unmap_vp(destination_port, destination_vpi, source_port, source_vpi) + + nio = atmsw.remove_nio(port) + nio.delete() + except DynamipsError as e: + self.send_custom_error(str(e)) + return + + # for now send back the original request + self.send_response(request) diff --git a/gns3server/modules/dynamips/backends/ethhub.py b/gns3server/modules/dynamips/backends/ethhub.py new file mode 100644 index 00000000..6060dede --- /dev/null +++ b/gns3server/modules/dynamips/backends/ethhub.py @@ -0,0 +1,162 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2014 GNS3 Technologies Inc. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from gns3server.modules import IModule +from ..nodes.hub import Hub +from ..dynamips_error import DynamipsError + +import logging +log = logging.getLogger(__name__) + + +class ETHHUB(object): + + @IModule.route("dynamips.ethhub.create") + def ethhub_create(self, request): + """ + Creates a new Ethernet switch. + + :param request: JSON request + """ + + name = None + if request and "name" in request: + name = request["name"] + + try: + hypervisor = self._hypervisor_manager.allocate_hypervisor_for_switch() + ethhub = Hub(hypervisor, name) + except DynamipsError as e: + self.send_custom_error(str(e)) + return + + response = {"name": ethhub.name, + "id": ethhub.id} + + self._ethernet_hubs[ethhub.id] = ethhub + self.send_response(response) + + @IModule.route("dynamips.ethhub.delete") + def ethhub_delete(self, request): + """ + Deletes a Ethernet hub. + + :param request: JSON request + """ + + #TODO: JSON schema validation for the request + log.debug("received request {}".format(request)) + ethhub_id = request["id"] + ethhub = self._ethernet_hubs[ethhub_id] + try: + ethhub.delete() + except DynamipsError as e: + self.send_custom_error(str(e)) + return + self.send_response(request) + + @IModule.route("dynamips.ethhub.update") + def ethhub_update(self, request): + """ + Updates a Ethernet hub. + + :param request: JSON request + """ + + #TODO: JSON schema validation for the request + log.debug("received request {}".format(request)) + ethhub_id = request["id"] + ethhub = self._ethernet_hubs[ethhub_id] + #ports = request["ports"] + + self.send_response(request) + + @IModule.route("dynamips.ethhub.allocate_udp_port") + def ethhub_allocate_udp_port(self, request): + """ + Allocates a UDP port in order to create an UDP NIO for an + Ethernet hub. + + :param request: JSON request + """ + + #TODO: JSON schema validation for the request + log.debug("received request {}".format(request)) + ethhub_id = request["id"] + ethhub = self._ethernet_hubs[ethhub_id] + + try: + # allocate a new UDP port + response = self.allocate_udp_port(ethhub) + except DynamipsError as e: + self.send_custom_error(str(e)) + return + + self.send_response(response) + + @IModule.route("dynamips.ethhub.add_nio") + def ethhub_add_nio(self, request): + """ + Adds an NIO (Network Input/Output) for an Ethernet hub. + + :param request: JSON request + """ + + #TODO: JSON schema validation for the request + log.debug("received request {}".format(request)) + ethhub_id = request["id"] + ethhub = self._ethernet_hubs[ethhub_id] + port = request["port"] + + try: + nio = self.create_nio(ethhub, request) + except DynamipsError as e: + self.send_custom_error(str(e)) + return + + try: + ethhub.add_nio(nio, port) + except DynamipsError as e: + self.send_custom_error(str(e)) + return + + # for now send back the original request + self.send_response(request) + + @IModule.route("dynamips.ethhub.delete_nio") + def ethsw_delete_nio(self, request): + """ + Deletes an NIO (Network Input/Output). + + :param request: JSON request + """ + + #TODO: JSON schema validation for the request + log.debug("received request {}".format(request)) + ethhub_id = request["id"] + ethhub = self._ethernet_hubs[ethhub_id] + port = request["port"] + + try: + nio = ethhub.remove_nio(port) + nio.delete() + except DynamipsError as e: + self.send_custom_error(str(e)) + return + + # for now send back the original request + self.send_response(request) diff --git a/gns3server/modules/dynamips/backends/ethsw.py b/gns3server/modules/dynamips/backends/ethsw.py new file mode 100644 index 00000000..490fcbae --- /dev/null +++ b/gns3server/modules/dynamips/backends/ethsw.py @@ -0,0 +1,180 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2014 GNS3 Technologies Inc. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from gns3server.modules import IModule +from ..nodes.ethernet_switch import EthernetSwitch +from ..dynamips_error import DynamipsError + +import logging +log = logging.getLogger(__name__) + + +class ETHSW(object): + + @IModule.route("dynamips.ethsw.create") + def ethsw_create(self, request): + """ + Creates a new Ethernet switch. + + :param request: JSON request + """ + + name = None + if request and "name" in request: + name = request["name"] + + try: + hypervisor = self._hypervisor_manager.allocate_hypervisor_for_switch() + ethsw = EthernetSwitch(hypervisor, name) + except DynamipsError as e: + self.send_custom_error(str(e)) + return + + response = {"name": ethsw.name, + "id": ethsw.id} + + self._ethernet_switches[ethsw.id] = ethsw + self.send_response(response) + + @IModule.route("dynamips.ethsw.delete") + def ethsw_delete(self, request): + """ + Deletes a Ethernet switch. + + :param request: JSON request + """ + + #TODO: JSON schema validation for the request + log.debug("received request {}".format(request)) + ethsw_id = request["id"] + ethsw = self._ethernet_switches[ethsw_id] + try: + ethsw.delete() + except DynamipsError as e: + self.send_custom_error(str(e)) + return + self.send_response(request) + + @IModule.route("dynamips.ethsw.update") + def ethsw_update(self, request): + """ + Updates a Ethernet switch. + + :param request: JSON request + """ + + #TODO: JSON schema validation for the request + log.debug("received request {}".format(request)) + ethsw_id = request["id"] + ethsw = self._ethernet_switches[ethsw_id] + ports = request["ports"] + + for port, info in ports.items(): + vlan = info["vlan"] + port_type = info["type"] + if port_type == "access": + ethsw.set_access_port(int(port), vlan) + elif port_type == "dot1q": + ethsw.set_dot1q_port(int(port), vlan) + elif port_type == "qinq": + ethsw.set_qinq_port(int(port), vlan) + + self.send_response(request) + + @IModule.route("dynamips.ethsw.allocate_udp_port") + def ethsw_allocate_udp_port(self, request): + """ + Allocates a UDP port in order to create an UDP NIO for an + Ethernet switch. + + :param request: JSON request + """ + + #TODO: JSON schema validation for the request + log.debug("received request {}".format(request)) + ethsw_id = request["id"] + ethsw = self._ethernet_switches[ethsw_id] + + try: + # allocate a new UDP port + response = self.allocate_udp_port(ethsw) + except DynamipsError as e: + self.send_custom_error(str(e)) + return + + self.send_response(response) + + @IModule.route("dynamips.ethsw.add_nio") + def ethsw_add_nio(self, request): + """ + Adds an NIO (Network Input/Output) for an Ethernet switch. + + :param request: JSON request + """ + + #TODO: JSON schema validation for the request + log.debug("received request {}".format(request)) + ethsw_id = request["id"] + ethsw = self._ethernet_switches[ethsw_id] + + port = request["port"] + vlan = request["vlan"] + port_type = request["port_type"] + try: + nio = self.create_nio(ethsw, request) + except DynamipsError as e: + self.send_custom_error(str(e)) + return + + try: + ethsw.add_nio(nio, port) + if port_type == "access": + ethsw.set_access_port(port, vlan) + elif port_type == "dot1q": + ethsw.set_dot1q_port(port, vlan) + elif port_type == "qinq": + ethsw.set_qinq_port(port, vlan) + except DynamipsError as e: + self.send_custom_error(str(e)) + return + + # for now send back the original request + self.send_response(request) + + @IModule.route("dynamips.ethsw.delete_nio") + def ethsw_delete_nio(self, request): + """ + Deletes an NIO (Network Input/Output). + + :param request: JSON request + """ + + #TODO: JSON schema validation for the request + log.debug("received request {}".format(request)) + ethsw_id = request["id"] + ethsw = self._ethernet_switches[ethsw_id] + port = request["port"] + + try: + nio = ethsw.remove_nio(port) + nio.delete() + except DynamipsError as e: + self.send_custom_error(str(e)) + return + + # for now send back the original request + self.send_response(request) diff --git a/gns3server/modules/dynamips/backends/frsw.py b/gns3server/modules/dynamips/backends/frsw.py new file mode 100644 index 00000000..99245172 --- /dev/null +++ b/gns3server/modules/dynamips/backends/frsw.py @@ -0,0 +1,179 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2014 GNS3 Technologies Inc. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from gns3server.modules import IModule +from ..nodes.frame_relay_switch import FrameRelaySwitch +from ..dynamips_error import DynamipsError + +import logging +log = logging.getLogger(__name__) + + +class FRSW(object): + + @IModule.route("dynamips.frsw.create") + def frsw_create(self, request): + """ + Creates a new Frame-Relay switch. + + :param request: JSON request + """ + + name = None + if request and "name" in request: + name = request["name"] + + try: + hypervisor = self._hypervisor_manager.allocate_hypervisor_for_switch() + frsw = FrameRelaySwitch(hypervisor, name) + except DynamipsError as e: + self.send_custom_error(str(e)) + return + + response = {"name": frsw.name, + "id": frsw.id} + + self._frame_relay_switches[frsw.id] = frsw + self.send_response(response) + + @IModule.route("dynamips.frsw.delete") + def frsw_delete(self, request): + """ + Deletes a Frame Relay switch. + + :param request: JSON request + """ + + #TODO: JSON schema validation for the request + log.debug("received request {}".format(request)) + frsw_id = request["id"] + frsw = self._frame_relay_switches[frsw_id] + try: + frsw.delete() + except DynamipsError as e: + self.send_custom_error(str(e)) + return + self.send_response(request) + + @IModule.route("dynamips.frsw.update") + def frsw_update(self, request): + """ + Updates a Frame Relay switch. + + :param request: JSON request + """ + + #TODO: JSON schema validation for the request + log.debug("received request {}".format(request)) + frsw_id = request["id"] + frsw = self._frame_relay_switches[frsw_id] + self.send_response(request) + + @IModule.route("dynamips.frsw.allocate_udp_port") + def frsw_allocate_udp_port(self, request): + """ + Allocates a UDP port in order to create an UDP NIO for an + Frame Relay switch. + + :param request: JSON request + """ + + #TODO: JSON schema validation for the request + log.debug("received request {}".format(request)) + frsw_id = request["id"] + frsw = self._frame_relay_switches[frsw_id] + + try: + # allocate a new UDP port + response = self.allocate_udp_port(frsw) + except DynamipsError as e: + self.send_custom_error(str(e)) + return + + self.send_response(response) + + @IModule.route("dynamips.frsw.add_nio") + def frsw_add_nio(self, request): + """ + Adds an NIO (Network Input/Output) for an Frame-Relay switch. + + :param request: JSON request + """ + + #TODO: JSON schema validation for the request + log.debug("received request {}".format(request)) + frsw_id = request["id"] + frsw = self._frame_relay_switches[frsw_id] + + port = request["port"] + mapping = request["mapping"] + + try: + nio = self.create_nio(frsw, request) + except DynamipsError as e: + self.send_custom_error(str(e)) + return + + try: + frsw.add_nio(nio, port) + + # add the VCs mapped with this port/nio + for source, destination in mapping.items(): + source_port, source_dlci = map(int, source.split(':')) + destination_port, destination_dlci = map(int, destination.split(':')) + if frsw.has_port(destination_port): + if (source_port, source_dlci) not in frsw.mapping and (destination_port, destination_dlci) not in frsw.mapping: + frsw.map_vc(source_port, source_dlci, destination_port, destination_dlci) + frsw.map_vc(destination_port, destination_dlci, source_port, source_dlci) + except DynamipsError as e: + self.send_custom_error(str(e)) + return + + # for now send back the original request + self.send_response(request) + + @IModule.route("dynamips.frsw.delete_nio") + def frsw_delete_nio(self, request): + """ + Deletes an NIO (Network Input/Output). + + :param request: JSON request + """ + + #TODO: JSON schema validation for the request + log.debug("received request {}".format(request)) + frsw_id = request["id"] + frsw = self._frame_relay_switches[frsw_id] + port = request["port"] + + try: + # remove the VCs mapped with this port/nio + for source, destination in frsw.mapping.copy().items(): + source_port, source_dlci = source + destination_port, destination_dlci = destination + if port == source_port: + frsw.unmap_vc(source_port, source_dlci, destination_port, destination_dlci) + frsw.unmap_vc(destination_port, destination_dlci, source_port, source_dlci) + + nio = frsw.remove_nio(port) + nio.delete() + except DynamipsError as e: + self.send_custom_error(str(e)) + return + + # for now send back the original request + self.send_response(request) diff --git a/gns3server/modules/dynamips/backends/vm.py b/gns3server/modules/dynamips/backends/vm.py new file mode 100644 index 00000000..27adf0a4 --- /dev/null +++ b/gns3server/modules/dynamips/backends/vm.py @@ -0,0 +1,367 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2014 GNS3 Technologies Inc. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import os +from gns3server.modules import IModule +from ..dynamips_error import DynamipsError + +from ..nodes.c1700 import C1700 +from ..nodes.c2600 import C2600 +from ..nodes.c2691 import C2691 +from ..nodes.c3600 import C3600 +from ..nodes.c3725 import C3725 +from ..nodes.c3745 import C3745 +from ..nodes.c7200 import C7200 + +from ..adapters.c7200_io_2fe import C7200_IO_2FE +from ..adapters.c7200_io_fe import C7200_IO_FE +from ..adapters.c7200_io_ge_e import C7200_IO_GE_E +from ..adapters.nm_16esw import NM_16ESW +from ..adapters.nm_1e import NM_1E +from ..adapters.nm_1fe_tx import NM_1FE_TX +from ..adapters.nm_4e import NM_4E +from ..adapters.nm_4t import NM_4T +from ..adapters.pa_2fe_tx import PA_2FE_TX +from ..adapters.pa_4e import PA_4E +from ..adapters.pa_4t import PA_4T +from ..adapters.pa_8e import PA_8E +from ..adapters.pa_8t import PA_8T +from ..adapters.pa_a1 import PA_A1 +from ..adapters.pa_fe_tx import PA_FE_TX +from ..adapters.pa_ge import PA_GE +from ..adapters.pa_pos_oc3 import PA_POS_OC3 +from ..adapters.wic_1enet import WIC_1ENET +from ..adapters.wic_1t import WIC_1T +from ..adapters.wic_2t import WIC_2T + +import logging +log = logging.getLogger(__name__) + + +ADAPTER_MATRIX = {"C7200_IO_2FE": C7200_IO_2FE, + "C7200_IO_FE": C7200_IO_FE, + "C7200-IO-GE-E": C7200_IO_GE_E, + "NM-16ESW": NM_16ESW, + "NM-1E": NM_1E, + "NM-1FE-TX": NM_1FE_TX, + "NM-4E": NM_4E, + "NM-4T": NM_4T, + "PA-2FE-TX": PA_2FE_TX, + "PA-4E": PA_4E, + "PA-4T+": PA_4T, + "PA-8E": PA_8E, + "PA-8T": PA_8T, + "PA-A1": PA_A1, + "PA-FE-TX": PA_FE_TX, + "PA-GE": PA_GE, + "PA-POS-OC3": PA_POS_OC3} + +WIC_MATRIX = {"WIC-1ENET": WIC_1ENET, + "WIC-1T": WIC_1T, + "WIC-2T": WIC_2T} + +PLATFORMS = {'c1700': C1700, + 'c2600': C2600, + 'c2691': C2691, + 'c3725': C3725, + 'c3745': C3745, + 'c3600': C3600, + 'c7200': C7200} + + +class VM(object): + + @IModule.route("dynamips.vm.create") + def vm_create(self, request): + """ + Creates a new VM (router). + + :param request: JSON request + """ + + if request == None: + self.send_param_error() + else: + log.debug("received request {}".format(request)) + + #TODO: JSON schema validation + #name = request["name"] + platform = request["platform"] + image = request["image"] + ram = request["ram"] + + try: + hypervisor = self._hypervisor_manager.allocate_hypervisor_for_router(image, ram) + router = PLATFORMS[platform](hypervisor) + router.ram = ram + router.image = image + router.sparsemem = self._hypervisor_manager.sparse_memory_support + router.mmap = self._hypervisor_manager.mmap_support + + # JIT sharing support + if self._hypervisor_manager.jit_sharing_support: + jitsharing_groups = hypervisor.jitsharing_groups + ios_image = os.path.basename(image) + if ios_image in jitsharing_groups: + router.jit_sharing_group = jitsharing_groups[ios_image] + else: + new_jit_group = -1 + for jit_group in range(0, 127): + if jit_group not in jitsharing_groups.values(): + new_jit_group = jit_group + break + if new_jit_group == -1: + raise DynamipsError("All JIT groups are allocated!") + router.jit_sharing_group = new_jit_group + + # Ghost IOS support + if self._hypervisor_manager.ghost_ios_support: + 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)) + return + + response = {"name": router.name, + "id": router.id} + defaults = router.defaults() + response.update(defaults) + self._routers[router.id] = router + self.send_response(response) + + @IModule.route("dynamips.vm.delete") + def vm_delete(self, request): + """ + Deletes a VM (router). + + :param request: JSON request + """ + + #TODO: JSON schema validation for the request + log.debug("received request {}".format(request)) + router_id = request["id"] + router = self._routers[router_id] + try: + router.delete() + except DynamipsError as e: + self.send_custom_error(str(e)) + return + self.send_response(request) + + @IModule.route("dynamips.vm.start") + def vm_start(self, request): + """ + Starts a VM (router) + + :param request: JSON request + """ + + #TODO: JSON schema validation for the request + log.debug("received request {}".format(request)) + router_id = request["id"] + router = self._routers[router_id] + try: + router.start() + except DynamipsError as e: + self.send_custom_error(str(e)) + return + self.send_response(request) + + @IModule.route("dynamips.vm.stop") + def vm_stop(self, request): + """ + Stops a VM (router) + + :param request: JSON request + """ + + #TODO: JSON schema validation for the request + log.debug("received request {}".format(request)) + router_id = request["id"] + router = self._routers[router_id] + try: + router.stop() + except DynamipsError as e: + self.send_custom_error(str(e)) + return + self.send_response(request) + + @IModule.route("dynamips.vm.suspend") + def vm_suspend(self, request): + """ + Suspends a VM (router) + + :param request: JSON request + """ + + #TODO: JSON schema validation for the request + log.debug("received request {}".format(request)) + router_id = request["id"] + router = self._routers[router_id] + try: + router.suspend() + except DynamipsError as e: + self.send_custom_error(str(e)) + return + self.send_response(request) + + @IModule.route("dynamips.vm.update") + def vm_update(self, request): + """ + Updates settings for a VM (router). + + :param request: JSON request + """ + + #TODO: JSON schema validation for the request + log.debug("received request {}".format(request)) + router_id = request["id"] + router = self._routers[router_id] + + # update the settings + for name, value in request.items(): + if hasattr(router, name) and getattr(router, name) != value: + try: + setattr(router, name, value) + except DynamipsError as e: + self.send_custom_error(str(e)) + return + elif name.startswith("slot") and value in ADAPTER_MATRIX: + slot_id = int(name[-1]) + adapter_name = value + adapter = ADAPTER_MATRIX[adapter_name]() + try: + if router.slots[slot_id] and type(router.slots[slot_id]) != type(adapter): + router.slot_remove_binding(slot_id) + router.slot_add_binding(slot_id, adapter) + except DynamipsError as e: + self.send_custom_error(str(e)) + return + elif name.startswith("slot") and value == None: + slot_id = int(name[-1]) + if router.slots[slot_id]: + try: + router.slot_remove_binding(slot_id) + except: + self.send_custom_error(str(e)) + return + elif name.startswith("wic") and value in WIC_MATRIX: + wic_slot_id = int(name[-1]) + wic_name = value + wic = WIC_MATRIX[wic_name]() + try: + if router.slots[0].wics[wic_slot_id] and type(router.slots[0].wics[wic_slot_id]) != type(wic): + router.uninstall_wic(wic_slot_id) + router.install_wic(wic_slot_id, wic) + except DynamipsError as e: + self.send_custom_error(str(e)) + return + 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) + + # Update the ghost IOS file in case the RAM size has changed + if self._hypervisor_manager.ghost_ios_support: + self.set_ghost_ios(router) + + # for now send back the original request + self.send_response(request) + + @IModule.route("dynamips.vm.allocate_udp_port") + def vm_allocate_udp_port(self, request): + """ + Allocates a UDP port in order to create an UDP NIO. + + :param request: JSON request + """ + + #TODO: JSON schema validation for the request + log.debug("received request {}".format(request)) + router_id = request["id"] + router = self._routers[router_id] + + try: + # allocate a new UDP port + response = self.allocate_udp_port(router) + except DynamipsError as e: + self.send_custom_error(str(e)) + return + + self.send_response(response) + + @IModule.route("dynamips.vm.add_nio") + def vm_add_nio(self, request): + """ + Adds an NIO (Network Input/Output) for a VM (router). + + :param request: JSON request + """ + + #TODO: JSON schema validation for the request + log.debug("received request {}".format(request)) + router_id = request["id"] + router = self._routers[router_id] + + slot = request["slot"] + port = request["port"] + + print(request) + + try: + nio = self.create_nio(router, request) + except DynamipsError as e: + self.send_custom_error(str(e)) + return + + try: + router.slot_add_nio_binding(slot, port, nio) + except DynamipsError as e: + self.send_custom_error(str(e)) + return + + # for now send back the original request + self.send_response(request) + + @IModule.route("dynamips.vm.delete_nio") + def vm_delete_nio(self, request): + """ + Deletes an NIO (Network Input/Output). + + :param request: JSON request + """ + + #TODO: JSON schema validation for the request + log.debug("received request {}".format(request)) + router_id = request["id"] + router = self._routers[router_id] + slot = request["slot"] + port = request["port"] + + try: + nio = router.slot_remove_nio_binding(slot, port) + nio.delete() + except DynamipsError as e: + self.send_custom_error(str(e)) + return + + # for now send back the original request + self.send_response(request) diff --git a/gns3server/modules/dynamips/dynamips_error.py b/gns3server/modules/dynamips/dynamips_error.py index 285e4a7b..6bca86f8 100644 --- a/gns3server/modules/dynamips/dynamips_error.py +++ b/gns3server/modules/dynamips/dynamips_error.py @@ -31,3 +31,7 @@ class DynamipsError(Exception): def __repr__(self): return self._message + + def __str__(self): + + return self._message diff --git a/gns3server/modules/dynamips/dynamips_hypervisor.py b/gns3server/modules/dynamips/dynamips_hypervisor.py index 0bedc7b7..98ab67f2 100644 --- a/gns3server/modules/dynamips/dynamips_hypervisor.py +++ b/gns3server/modules/dynamips/dynamips_hypervisor.py @@ -21,6 +21,7 @@ http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L46 """ import socket +import errno import re import logging from .dynamips_error import DynamipsError @@ -54,6 +55,7 @@ class DynamipsHypervisor(object): self._baseconsole = 2000 self._baseaux = 2100 self._baseudp = 10000 + self._current_udp_port = self._baseudp self._version = "N/A" self._timeout = 30 self._socket = None @@ -145,7 +147,7 @@ class DynamipsHypervisor(object): @working_dir.setter def working_dir(self, working_dir): """ - Set the working directory for this hypervisor. + Sets the working directory for this hypervisor. :param working_dir: path to the working directory """ @@ -198,7 +200,7 @@ class DynamipsHypervisor(object): @devices.setter def devices(self, devices): """ - Set the list of devices managed by this hypervisor instance. + Sets the list of devices managed by this hypervisor instance. This method is for internal use. :param devices: a list of device objects @@ -219,7 +221,7 @@ class DynamipsHypervisor(object): @baseconsole.setter def baseconsole(self, baseconsole): """ - Set the base console TCP port for this hypervisor. + Sets the base console TCP port for this hypervisor. :param baseconsole: base console value (integer) """ @@ -239,7 +241,7 @@ class DynamipsHypervisor(object): @baseaux.setter def baseaux(self, baseaux): """ - Set the base auxiliary TCP port for this hypervisor. + Sets the base auxiliary TCP port for this hypervisor. :param baseaux: base auxiliary port value (integer) """ @@ -259,12 +261,16 @@ class DynamipsHypervisor(object): @baseudp.setter def baseudp(self, baseudp): """ - Set the next open UDP port for NIOs for this hypervisor. + Sets the next open UDP port for NIOs for this hypervisor. :param baseudp: base UDP port value (integer) """ self._baseudp = baseudp + self._current_udp_port = self._baseudp + + #FIXME + log.info("hypervisor a new base UDP {}".format(self._baseudp)) @property def ghosts(self): @@ -294,7 +300,7 @@ class DynamipsHypervisor(object): :returns: JIT sharing groups dict (image_name -> group number) """ - return self._ghosts + return self._jitsharing_groups def add_jitsharing_group(self, image_name, group_number): """ @@ -326,6 +332,42 @@ class DynamipsHypervisor(object): return self._port + def allocate_udp_port(self, max_port=100): + """ + Allocates a new UDP port for creating an UDP NIO. + + :param max_port: maximum number of port to scan in + order to find one available for use. + + :returns: port number (integer) + """ + + #FIXME: better check for IPv6 + start_port = self._current_udp_port + print("start port = {}".format(start_port)) + end_port = start_port + max_port + for port in range(start_port, end_port): + print(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 + print("current port = {}".format(self._current_udp_port)) + 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)) + def send_raw(self, string): """ Sends a raw command to this hypervisor. Use sparingly. diff --git a/gns3server/modules/dynamips/hypervisor.py b/gns3server/modules/dynamips/hypervisor.py index 3a8933f3..31e86909 100644 --- a/gns3server/modules/dynamips/hypervisor.py +++ b/gns3server/modules/dynamips/hypervisor.py @@ -20,6 +20,7 @@ Represents a Dynamips hypervisor and starts/stops the associated Dynamips proces """ import os +import time import subprocess import logging @@ -82,7 +83,7 @@ class Hypervisor(DynamipsHypervisor): @path.setter def path(self, path): """ - Set the path to the Dynamips executable. + Sets the path to the Dynamips executable. :param path: path to Dynamips """ @@ -102,7 +103,7 @@ class Hypervisor(DynamipsHypervisor): @port.setter def port(self, port): """ - Set the port used to start the Dynamips hypervisor. + Sets the port used to start the Dynamips hypervisor. :param port: port number (integer) """ @@ -122,7 +123,7 @@ class Hypervisor(DynamipsHypervisor): @host.setter def host(self, host): """ - Set the host (binding) used to start the Dynamips hypervisor. + Sets the host (binding) used to start the Dynamips hypervisor. :param host: host/address (string) """ @@ -142,7 +143,7 @@ class Hypervisor(DynamipsHypervisor): @workingdir.setter def workingdir(self, workingdir): """ - Set the working directory used to start the Dynamips hypervisor. + Sets the working directory used to start the Dynamips hypervisor. :param workingdir: path to a working directory """ @@ -163,7 +164,7 @@ class Hypervisor(DynamipsHypervisor): @image_ref.setter def image_ref(self, ios_image_name): """ - Set the reference IOS image name + Sets the reference IOS image name (used by the hypervisor manager for load-balancing purposes). :param ios_image_name: image reference name @@ -230,8 +231,13 @@ class Hypervisor(DynamipsHypervisor): """ if self.is_running(): + DynamipsHypervisor.stop(self) logger.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) self._process.kill() + self._process.wait() def read_stdout(self): """ diff --git a/gns3server/modules/dynamips/hypervisor_manager.py b/gns3server/modules/dynamips/hypervisor_manager.py index d759cd1b..f675f070 100644 --- a/gns3server/modules/dynamips/hypervisor_manager.py +++ b/gns3server/modules/dynamips/hypervisor_manager.py @@ -45,27 +45,29 @@ class HypervisorManager(object): path, workingdir, host='127.0.0.1', - base_port=7200, - base_console=2000, - base_aux=3000, - base_udp=10000): + base_hypervisor_port=7200, + base_console_port=2000, + base_aux_port=3000, + base_udp_port=10000): self._hypervisors = [] self._path = path self._workingdir = workingdir - self._base_port = base_port - self._current_port = self._base_port - self._base_console = base_console - self._base_aux = base_aux - self._base_udp = base_udp self._host = host - self._clean_workingdir = False - self._ghost_ios = True - self._mmap = True - self._jit_sharing = False - self._sparsemem = True + self._base_hypervisor_port = base_hypervisor_port + self._current_port = self._base_hypervisor_port + self._base_console_port = base_console_port + self._base_aux_port = base_aux_port + self._base_udp_port = base_udp_port + self._current_base_udp_port = self._base_udp_port + self._udp_incrementation_per_hypervisor = 100 + self._ghost_ios_support = True + self._mmap_support = True + self._jit_sharing_support = False + self._sparse_memory_support = True + self._allocate_hypervisor_per_device = True self._memory_usage_limit_per_hypervisor = 1024 - self._group_ios_per_hypervisor = True + self._allocate_hypervisor_per_ios_image = True def __del__(self): """ @@ -84,6 +86,263 @@ class HypervisorManager(object): return self._hypervisors + @property + def path(self): + """ + Returns the Dynamips path. + + :returns: path to Dynamips + """ + + return self._path + + @path.setter + def path(self, path): + """ + Set a new Dynamips path. + + :param path: path to Dynamips + """ + + self._path = path + log.info("Dynamips path set to {}".format(self._path)) + + @property + def workingdir(self): + """ + Returns the Dynamips working directory path. + + :returns: path to Dynamips working directory + """ + + return self._workingdir + + @workingdir.setter + def workingdir(self, workingdir): + """ + Sets a new path to the Dynamips working directory. + + :param workingdir: path to Dynamips working directory + """ + + self._workingdir = workingdir + log.info("working directory set to {}".format(self._workingdir)) + + @property + def base_hypervisor_port(self): + """ + Returns the base hypervisor port. + + :returns: base hypervisor port (integer) + """ + + return self._base_hypervisor_port + + @base_hypervisor_port.setter + def base_hypervisor_port(self, base_hypervisor_port): + """ + Set a new base hypervisor port. + + :param base_hypervisor_port: base hypervisor port (integer) + """ + + if self._base_hypervisor_port != base_hypervisor_port: + self._base_hypervisor_port = base_hypervisor_port + self._current_port = self._base_hypervisor_port + log.info("base hypervisor port set to {}".format(self._base_hypervisor_port)) + + @property + def base_console_port(self): + """ + Returns the base console port. + + :returns: base console port (integer) + """ + + return self._base_console_port + + @base_console_port.setter + def base_console_port(self, base_console_port): + """ + Set a new base console port. + + :param base_console_port: base console port (integer) + """ + + if self._base_console_port != base_console_port: + self._base_console_port = base_console_port + log.info("base console port set to {}".format(self._base_console_port)) + + @property + def base_aux_port(self): + """ + Returns the base auxiliary console port. + + :returns: base auxiliary console port (integer) + """ + + return self._base_aux_port + + @base_aux_port.setter + def base_aux_port(self, base_aux_port): + """ + Set a new base auxiliary console port. + + :param base_aux_port: base auxiliary console port (integer) + """ + + if self._base_aux_port != base_aux_port: + self._base_aux_port = base_aux_port + log.info("base aux port set to {}".format(self._base_aux_port)) + + @property + def base_udp_port(self): + """ + Returns the base UDP port. + + :returns: base UDP port (integer) + """ + + return self._base_udp_port + + @base_udp_port.setter + def base_udp_port(self, base_udp_port): + """ + Set a new base UDP port. + + :param base_udp_port: base UDP port (integer) + """ + + if self._base_udp_port != base_udp_port: + self._base_udp_port = base_udp_port + self._current_base_udp_port = self._base_udp_port + log.info("base UDP port set to {}".format(self._base_udp_port)) + + @property + def ghost_ios_support(self): + """ + Returns either ghost IOS is activated or not. + + :returns: boolean + """ + + return self._ghost_ios_support + + @ghost_ios_support.setter + def ghost_ios_support(self, ghost_ios_support): + """ + Sets ghost IOS support. + + :param ghost_ios_support: boolean + """ + + if self._ghost_ios_support != ghost_ios_support: + self._ghost_ios_support = ghost_ios_support + if ghost_ios_support: + log.info("ghost IOS support enabled") + else: + log.info("ghost IOS support disabled") + + @property + def mmap_support(self): + """ + Returns either mmap is activated or not. + + :returns: boolean + """ + + return self._mmap_support + + @mmap_support.setter + def mmap_support(self, mmap_support): + """ + Sets mmap support. + + :param mmap_support: boolean + """ + + if self._mmap_support != mmap_support: + self._mmap_support = mmap_support + if mmap_support: + log.info("mmap support enabled") + else: + log.info("mmap support disabled") + + @property + def sparse_memory_support(self): + """ + Returns either sparse memory is activated or not. + + :returns: boolean + """ + + return self._sparse_memory_support + + @sparse_memory_support.setter + def sparse_memory_support(self, sparse_memory_support): + """ + Sets sparse memory support. + + :param sparse_memory_support: boolean + """ + + if self._sparse_memory_support != sparse_memory_support: + self._sparse_memory_support = sparse_memory_support + if sparse_memory_support: + log.info("sparse memory support enabled") + else: + log.info("sparse memory support disabled") + + @property + def jit_sharing_support(self): + """ + Returns either JIT sharing is activated or not. + + :returns: boolean + """ + + return self._jit_sharing_support + + @jit_sharing_support.setter + def jit_sharing_support(self, jit_sharing_support): + """ + Sets JIT sharing support. + + :param jit_sharing_support: boolean + """ + + if self._jit_sharing_support != jit_sharing_support: + self._jit_sharing_support = jit_sharing_support + if jit_sharing_support: + log.info("JIT sharing support enabled") + else: + log.info("JIT sharing support disabled") + + @property + def allocate_hypervisor_per_device(self): + """ + Returns either an hypervisor is created for each device. + + :returns: True or False + """ + + return self._allocate_hypervisor_per_device + + @allocate_hypervisor_per_device.setter + def allocate_hypervisor_per_device(self, value): + """ + Sets if an hypervisor is created for each device. + + :param value: True or False + """ + + if self._allocate_hypervisor_per_device != value: + self._allocate_hypervisor_per_device = value + if value: + log.info("allocating an hypervisor per device enabled") + else: + log.info("allocating an hypervisor per device disabled") + @property def memory_usage_limit_per_hypervisor(self): """ @@ -97,15 +356,17 @@ class HypervisorManager(object): @memory_usage_limit_per_hypervisor.setter def memory_usage_limit_per_hypervisor(self, memory_limit): """ - Set the memory usage limit per hypervisor + Sets the memory usage limit per hypervisor :param memory_limit: memory limit value (integer) """ - self._memory_usage_limit_per_hypervisor = memory_limit + if self._memory_usage_limit_per_hypervisor != memory_limit: + self._memory_usage_limit_per_hypervisor = memory_limit + log.info("memory usage limit per hypervisor set to {}".format(memory_limit)) @property - def group_ios_per_hypervisor(self): + def allocate_hypervisor_per_ios_image(self): """ Returns if router are grouped per hypervisor based on their IOS image. @@ -113,18 +374,23 @@ class HypervisorManager(object): :returns: True or False """ - return self._group_ios_per_hypervisor + return self._allocate_hypervisor_per_ios_image - @group_ios_per_hypervisor.setter - def group_ios_per_hypervisor(self, value): + @allocate_hypervisor_per_ios_image.setter + def allocate_hypervisor_per_ios_image(self, value): """ - Set if router are grouped per hypervisor + Sets if routers are grouped per hypervisor based on their IOS image. :param value: True or False """ - self._group_ios_per_hypervisor = value + if self._allocate_hypervisor_per_ios_image != value: + self._allocate_hypervisor_per_ios_image = value + if value: + log.info("allocating an hypervisor per IOS image enabled") + else: + log.info("allocating an hypervisor per IOS image disabled") def wait_for_hypervisor(self, host, port, timeout=10): """ @@ -172,6 +438,10 @@ class HypervisorManager(object): log.info("hypervisor {}:{} has successfully started".format(hypervisor.host, hypervisor.port)) hypervisor.connect() + hypervisor.baseconsole = self._base_console_port + hypervisor.baseaux = self._base_aux_port + 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 @@ -187,16 +457,21 @@ class HypervisorManager(object): :returns: the allocated hypervisor object """ - for hypervisor in self._hypervisors: - if self._group_ios_per_hypervisor and hypervisor.image_ref != router_ios_image: - continue - if (hypervisor.memory_load + router_ram) <= self._memory_usage_limit_per_hypervisor: - current_memory_load = hypervisor.memory_load - hypervisor.increase_memory_load(router_ram) - log.info("allocating existing hypervisor {}:{}, RAM={}+{}".format(hypervisor.host, - hypervisor.port, - current_memory_load, - router_ram)) + # allocate an hypervisor for each router by default + if not self._allocate_hypervisor_per_device: + for hypervisor in self._hypervisors: + if self._allocate_hypervisor_per_ios_image: + if not hypervisor.image_ref: + hypervisor.image_ref = router_ios_image + elif hypervisor.image_ref != router_ios_image: + continue + if (hypervisor.memory_load + router_ram) <= self._memory_usage_limit_per_hypervisor: + current_memory_load = hypervisor.memory_load + hypervisor.increase_memory_load(router_ram) + log.info("allocating existing hypervisor {}:{}, RAM={}+{}".format(hypervisor.host, + hypervisor.port, + current_memory_load, + router_ram)) return hypervisor hypervisor = self.start_new_hypervisor() @@ -226,6 +501,22 @@ class HypervisorManager(object): hypervisor.stop() self._hypervisors.remove(hypervisor) + def allocate_hypervisor_for_switch(self): + """ + Allocates a Dynamips hypervisor for a specific switch + + :returns: the allocated hypervisor object + """ + + # For now always allocate the first hypervisor available, + # in the future we could randomly allocate. + + if self._hypervisors: + return self._hypervisors[0] + + # no hypervisor, let's start one! + return self.start_new_hypervisor() + def stop_all_hypervisors(self): """ Stops all hypervisors. @@ -233,3 +524,4 @@ class HypervisorManager(object): for hypervisor in self._hypervisors: hypervisor.stop() + self._hypervisors = [] diff --git a/gns3server/modules/dynamips/nios/nio.py b/gns3server/modules/dynamips/nios/nio.py index 41346ce5..fd00af61 100644 --- a/gns3server/modules/dynamips/nios/nio.py +++ b/gns3server/modules/dynamips/nios/nio.py @@ -22,6 +22,9 @@ http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L451 from ..dynamips_error import DynamipsError +import logging +log = logging.getLogger(__name__) + class NIO(object): """ @@ -55,6 +58,7 @@ class NIO(object): """ self._hypervisor.send("nio delete {}".format(self._name)) + log.info("NIO {name} has been deleted".format(name=self._name)) def rename(self, new_name): """ @@ -65,6 +69,9 @@ class NIO(object): self._hypervisor.send("nio rename {name} {new_name}".format(name=self._name, new_name=new_name)) + + log.info("NIO {name} renamed to {new_name}".format(name=self._name, + new_name=new_name)) self._name = new_name def debug(self, debug): @@ -197,7 +204,7 @@ class NIO(object): def set_bandwidth(self, bandwidth): """ - Set bandwidth constraint. + Sets bandwidth constraint. :param bandwidth: bandwidth integer value (in Kb/s) """ diff --git a/gns3server/modules/dynamips/nios/nio_fifo.py b/gns3server/modules/dynamips/nios/nio_fifo.py index e4153f53..6ddf5c8c 100644 --- a/gns3server/modules/dynamips/nios/nio_fifo.py +++ b/gns3server/modules/dynamips/nios/nio_fifo.py @@ -21,6 +21,9 @@ Interface for FIFO NIOs. from .nio import NIO +import logging +log = logging.getLogger(__name__) + class NIO_FIFO(NIO): """ @@ -42,6 +45,16 @@ class NIO_FIFO(NIO): self._hypervisor.send("nio create_fifo {}".format(self._name)) + log.info("NIO FIFO {name} created.".format(name=self._name)) + + @classmethod + def reset(cls): + """ + Reset the instance count. + """ + + cls._instance_count = 0 + def crossconnect(self, nio): """ Establishes a cross-connect between this FIFO NIO and another one. @@ -51,3 +64,5 @@ class NIO_FIFO(NIO): self._hypervisor.send("nio crossconnect_fifo {name} {nio}".format(name=self._name, nio=nio)) + + log.info("NIO FIFO {name} crossconnected with {nio_name}.".format(name=self._name, nio_name=nio.name)) diff --git a/gns3server/modules/dynamips/nios/nio_generic_ethernet.py b/gns3server/modules/dynamips/nios/nio_generic_ethernet.py index 48a2c9c5..928da378 100644 --- a/gns3server/modules/dynamips/nios/nio_generic_ethernet.py +++ b/gns3server/modules/dynamips/nios/nio_generic_ethernet.py @@ -21,6 +21,9 @@ Interface for generic Ethernet NIOs (PCAP library). from .nio import NIO +import logging +log = logging.getLogger(__name__) + class NIO_GenericEthernet(NIO): """ @@ -45,6 +48,17 @@ class NIO_GenericEthernet(NIO): self._hypervisor.send("nio create_gen_eth {name} {eth_device}".format(name=self._name, eth_device=ethernet_device)) + log.info("NIO Generic Ethernet {name} created with device {device}".format(name=self._name, + device=ethernet_device)) + + @classmethod + def reset(cls): + """ + Reset the instance count. + """ + + cls._instance_count = 0 + @property def ethernet_device(self): """ diff --git a/gns3server/modules/dynamips/nios/nio_linux_ethernet.py b/gns3server/modules/dynamips/nios/nio_linux_ethernet.py index afee21b9..da44ebdf 100644 --- a/gns3server/modules/dynamips/nios/nio_linux_ethernet.py +++ b/gns3server/modules/dynamips/nios/nio_linux_ethernet.py @@ -21,6 +21,9 @@ Interface for Linux Ethernet NIOs (Linux only). from .nio import NIO +import logging +log = logging.getLogger(__name__) + class NIO_LinuxEthernet(NIO): """ @@ -45,6 +48,17 @@ class NIO_LinuxEthernet(NIO): self._hypervisor.send("nio create_linux_eth {name} {eth_device}".format(name=self._name, eth_device=ethernet_device)) + log.info("NIO Linux Ethernet {name} created with device {device}".format(name=self._name, + device=ethernet_device)) + + @classmethod + def reset(cls): + """ + Reset the instance count. + """ + + cls._instance_count = 0 + @property def ethernet_device(self): """ diff --git a/gns3server/modules/dynamips/nios/nio_mcast.py b/gns3server/modules/dynamips/nios/nio_mcast.py index 6b80fd65..c16f9cff 100644 --- a/gns3server/modules/dynamips/nios/nio_mcast.py +++ b/gns3server/modules/dynamips/nios/nio_mcast.py @@ -21,6 +21,9 @@ Interface for multicast NIOs. from .nio import NIO +import logging +log = logging.getLogger(__name__) + class NIO_Mcast(NIO): """ @@ -49,6 +52,18 @@ class NIO_Mcast(NIO): mgroup=group, mport=port)) + log.info("NIO Multicast {name} created with mgroup={group}, mport={port}".format(name=self._name, + group=group, + port=port)) + + @classmethod + def reset(cls): + """ + Reset the instance count. + """ + + cls._instance_count = 0 + @property def group(self): """ @@ -82,7 +97,7 @@ class NIO_Mcast(NIO): @ttl.setter def ttl(self, ttl): """ - Set the TTL for the multicast address + Sets the TTL for the multicast address :param ttl: TTL value """ diff --git a/gns3server/modules/dynamips/nios/nio_null.py b/gns3server/modules/dynamips/nios/nio_null.py index 7795e2c4..379fe2a3 100644 --- a/gns3server/modules/dynamips/nios/nio_null.py +++ b/gns3server/modules/dynamips/nios/nio_null.py @@ -21,6 +21,9 @@ Interface for dummy NIOs (mostly for tests). from .nio import NIO +import logging +log = logging.getLogger(__name__) + class NIO_Null(NIO): """ @@ -41,3 +44,12 @@ class NIO_Null(NIO): self._name = 'nio_null' + str(self._id) self._hypervisor.send("nio create_null {}".format(self._name)) + log.info("NIO NULL {name} created.".format(name=self._name)) + + @classmethod + def reset(cls): + """ + Reset the instance count. + """ + + cls._instance_count = 0 diff --git a/gns3server/modules/dynamips/nios/nio_tap.py b/gns3server/modules/dynamips/nios/nio_tap.py index 28ce710d..ffaf6d56 100644 --- a/gns3server/modules/dynamips/nios/nio_tap.py +++ b/gns3server/modules/dynamips/nios/nio_tap.py @@ -21,6 +21,9 @@ Interface for TAP NIOs (UNIX based OSes only). from .nio import NIO +import logging +log = logging.getLogger(__name__) + class NIO_TAP(NIO): """ @@ -45,6 +48,17 @@ class NIO_TAP(NIO): self._hypervisor.send("nio create_tap {name} {tap}".format(name=self._name, tap=tap_device)) + log.info("NIO TAP {name} created with device {device}".format(name=self._name, + device=tap_device)) + + @classmethod + def reset(cls): + """ + Reset the instance count. + """ + + cls._instance_count = 0 + @property def tap_device(self): """ diff --git a/gns3server/modules/dynamips/nios/nio_udp.py b/gns3server/modules/dynamips/nios/nio_udp.py index 6021cfe9..9005c3c6 100644 --- a/gns3server/modules/dynamips/nios/nio_udp.py +++ b/gns3server/modules/dynamips/nios/nio_udp.py @@ -21,6 +21,9 @@ Interface for UDP NIOs. from .nio import NIO +import logging +log = logging.getLogger(__name__) + class NIO_UDP(NIO): """ @@ -51,6 +54,19 @@ class NIO_UDP(NIO): rhost=rhost, rport=rport)) + log.info("NIO UDP {name} created with lport={lport}, rhost={rhost}, rport={rport}".format(name=self._name, + lport=lport, + rhost=rhost, + rport=rport)) + + @classmethod + def reset(cls): + """ + Reset the instance count. + """ + + cls._instance_count = 0 + @property def lport(self): """ diff --git a/gns3server/modules/dynamips/nios/nio_udp_auto.py b/gns3server/modules/dynamips/nios/nio_udp_auto.py index eedbbcd5..a8a9f70a 100644 --- a/gns3server/modules/dynamips/nios/nio_udp_auto.py +++ b/gns3server/modules/dynamips/nios/nio_udp_auto.py @@ -21,6 +21,9 @@ Interface for automatic UDP NIOs. from .nio import NIO +import logging +log = logging.getLogger(__name__) + class NIO_UDP_auto(NIO): """ @@ -48,9 +51,22 @@ class NIO_UDP_auto(NIO): laddr=laddr, lport_start=lport_start, lport_end=lport_end))[0]) + + log.info("NIO UDP AUTO {name} created with laddr={laddr}, lport_start={start}, lport_end={end}".format(name=self._name, + laddr=laddr, + start=lport_start, + end=lport_end)) self._raddr = None self._rport = None + @classmethod + def reset(cls): + """ + Reset the instance count. + """ + + cls._instance_count = 0 + @property def laddr(self): """ @@ -104,3 +120,7 @@ class NIO_UDP_auto(NIO): rport=rport)) self._raddr = raddr self._rport = rport + + log.info("NIO UDP AUTO {name} connected to {raddr}:{rport}".format(name=self._name, + raddr=raddr, + rport=rport)) diff --git a/gns3server/modules/dynamips/nios/nio_unix.py b/gns3server/modules/dynamips/nios/nio_unix.py index 304c7f72..07e6df68 100644 --- a/gns3server/modules/dynamips/nios/nio_unix.py +++ b/gns3server/modules/dynamips/nios/nio_unix.py @@ -21,6 +21,9 @@ Interface for UNIX NIOs (Unix based OSes only). from .nio import NIO +import logging +log = logging.getLogger(__name__) + class NIO_UNIX(NIO): """ @@ -48,6 +51,18 @@ class NIO_UNIX(NIO): local=local_file, remote=remote_file)) + log.info("NIO UNIX {name} created with local file {local} and remote file {remote}".format(name=self._name, + local=local_file, + remote=remote_file)) + + @classmethod + def reset(cls): + """ + Reset the instance count. + """ + + cls._instance_count = 0 + @property def local_file(self): """ diff --git a/gns3server/modules/dynamips/nios/nio_vde.py b/gns3server/modules/dynamips/nios/nio_vde.py index 9f4df297..e8f14e9c 100644 --- a/gns3server/modules/dynamips/nios/nio_vde.py +++ b/gns3server/modules/dynamips/nios/nio_vde.py @@ -21,6 +21,9 @@ Interface for VDE (Virtual Distributed Ethernet) NIOs (Unix based OSes only). from .nio import NIO +import logging +log = logging.getLogger(__name__) + class NIO_VDE(NIO): """ @@ -48,6 +51,18 @@ class NIO_VDE(NIO): control=control_file, local=local_file)) + log.info("NIO VDE {name} created with control={control}, local={local}".format(name=self._name, + control=control_file, + local=local_file)) + + @classmethod + def reset(cls): + """ + Reset the instance count. + """ + + cls._instance_count = 0 + @property def control_file(self): """ diff --git a/gns3server/modules/dynamips/nodes/atm_switch.py b/gns3server/modules/dynamips/nodes/atm_switch.py index 99474912..b689d62e 100644 --- a/gns3server/modules/dynamips/nodes/atm_switch.py +++ b/gns3server/modules/dynamips/nodes/atm_switch.py @@ -23,6 +23,9 @@ http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L593 from __future__ import unicode_literals from ..dynamips_error import DynamipsError +import logging +log = logging.getLogger(__name__) + class ATMSwitch(object): """ @@ -32,15 +35,47 @@ class ATMSwitch(object): :param name: name for this switch """ - def __init__(self, hypervisor, name): + _instance_count = 1 + + def __init__(self, hypervisor, name=None): + + # create an unique ID + self._id = ATMSwitch._instance_count + ATMSwitch._instance_count += 1 + + # let's create a unique name if none has been chosen + if not name: + name = "ATM" + str(self._id) self._hypervisor = hypervisor self._name = '"' + name + '"' # put name into quotes to protect spaces self._hypervisor.send("atmsw create {}".format(self._name)) + + log.info("ATM switch {name} [id={id}] has been created".format(name=self._name, + id=self._id)) + self._hypervisor.devices.append(self) self._nios = {} self._mapping = {} + @classmethod + def reset(cls): + """ + Reset the instance count. + """ + + cls._instance_count = 1 + + @property + def id(self): + """ + Returns the unique ID for this ATM switch. + + :returns: id (integer) + """ + + return self._id + @property def name(self): """ @@ -100,6 +135,11 @@ class ATMSwitch(object): new_name = '"' + new_name + '"' # put the new name into quotes to protect spaces self._hypervisor.send("atmsw rename {name} {new_name}".format(name=self._name, new_name=new_name)) + + log.info("ATM switch {name} [id={id}]: renamed to {new_name}".format(name=self._name, + id=self._id, + new_name=new_name)) + self._name = new_name def delete(self): @@ -108,8 +148,22 @@ class ATMSwitch(object): """ self._hypervisor.send("atmsw delete {}".format(self._name)) + + log.info("ATM switch {name} [id={id}] has been deleted".format(name=self._name, + id=self._id)) self._hypervisor.devices.remove(self) + def has_port(self, port): + """ + Checks if a port exists on this ATM switch. + + :returns: boolean + """ + + if port in self._nios: + return True + return False + def add_nio(self, nio, port): """ Adds a NIO as new port on ATM switch. @@ -121,6 +175,11 @@ class ATMSwitch(object): if port in self._nios: raise DynamipsError("Port {} isn't free".format(port)) + log.info("ATM switch {name} [id={id}]: NIO {nio} bound to port {port}".format(name=self._name, + id=self._id, + nio=nio, + port=port)) + self._nios[port] = nio def remove_nio(self, port): @@ -133,7 +192,14 @@ class ATMSwitch(object): if port not in self._nios: raise DynamipsError("Port {} is not allocated".format(port)) + nio = self._nios[port] + log.info("ATM switch {name} [id={id}]: NIO {nio} removed from port {port}".format(name=self._name, + id=self._id, + nio=nio, + port=port)) + del self._nios[port] + return nio def map_vp(self, port1, vpi1, port2, vpi2): """ @@ -159,6 +225,14 @@ class ATMSwitch(object): input_vpi=vpi1, output_nio=nio2, output_vpi=vpi2)) + + log.info("ATM switch {name} [id={id}]: VPC from port {port1} VPI {vpi1} to port {port2} VPI {vpi2} created".format(name=self._name, + id=self._id, + port1=port1, + vpi1=vpi1, + port2=port2, + vpi2=vpi2)) + self._mapping[(port1, vpi1)] = (port2, vpi2) def unmap_vp(self, port1, vpi1, port2, vpi2): @@ -185,6 +259,14 @@ class ATMSwitch(object): input_vpi=vpi1, output_nio=nio2, output_vpi=vpi2)) + + log.info("ATM switch {name} [id={id}]: VPC from port {port1} VPI {vpi1} to port {port2} VPI {vpi2} deleted".format(name=self._name, + id=self._id, + port1=port1, + vpi1=vpi1, + port2=port2, + vpi2=vpi2)) + del self._mapping[(port1, vpi1)] def map_pvc(self, port1, vpi1, vci1, port2, vpi2, vci2): @@ -215,6 +297,16 @@ class ATMSwitch(object): output_nio=nio2, output_vpi=vpi2, output_vci=vci2)) + + log.info("ATM switch {name} [id={id}]: VCC from port {port1} VPI {vpi1} VCI {vci1} to port {port2} VPI {vpi2} VCI {vci2} created".format(name=self._name, + id=self._id, + port1=port1, + vpi1=vpi1, + vci1=vci1, + port2=port2, + vpi2=vpi2, + vci2=vci2)) + self._mapping[(port1, vpi1, vci1)] = (port2, vpi2, vci2) def unmap_pvc(self, port1, vpi1, vci1, port2, vpi2, vci2): @@ -245,4 +337,13 @@ class ATMSwitch(object): output_nio=nio2, output_vpi=vpi2, output_vci=vci2)) + + log.info("ATM switch {name} [id={id}]: VCC from port {port1} VPI {vpi1} VCI {vci1} to port {port2} VPI {vpi2} VCI {vci2} deleted".format(name=self._name, + id=self._id, + port1=port1, + vpi1=vpi1, + vci1=vci1, + port2=port2, + vpi2=vpi2, + vci2=vci2)) del self._mapping[(port1, vpi1, vci1)] diff --git a/gns3server/modules/dynamips/nodes/bridge.py b/gns3server/modules/dynamips/nodes/bridge.py index 9e5a8af1..aa2b7eb0 100644 --- a/gns3server/modules/dynamips/nodes/bridge.py +++ b/gns3server/modules/dynamips/nodes/bridge.py @@ -88,6 +88,7 @@ class Bridge(object): new_name = '"' + new_name + '"' # put the new name into quotes to protect spaces self._hypervisor.send("nio_bridge rename {name} {new_name}".format(name=self._name, new_name=new_name)) + self._name = new_name def delete(self): diff --git a/gns3server/modules/dynamips/nodes/c1700.py b/gns3server/modules/dynamips/nodes/c1700.py index 96b4ec72..154f963f 100644 --- a/gns3server/modules/dynamips/nodes/c1700.py +++ b/gns3server/modules/dynamips/nodes/c1700.py @@ -25,6 +25,9 @@ from .router import Router from ..adapters.c1700_mb_1fe import C1700_MB_1FE from ..adapters.c1700_mb_wic1 import C1700_MB_WIC1 +import logging +log = logging.getLogger(__name__) + class C1700(Router): """ @@ -37,7 +40,7 @@ class C1700(Router): 1710 is not supported. """ - def __init__(self, hypervisor, name, chassis="1720"): + def __init__(self, hypervisor, name=None, chassis="1720"): Router.__init__(self, hypervisor, name, platform="c1700") # Set default values for this platform @@ -54,6 +57,27 @@ class C1700(Router): self._setup_chassis() + def defaults(self): + """ + Returns all the default attribute values for this platform. + + :returns: default values (dictionary) + """ + + router_defaults = Router.defaults(self) + + platform_defaults = {"ram": self._ram, + "nvram": self._nvram, + "disk0": self._disk0, + "disk1": self._disk1, + "chassis": self._chassis, + "iomem": self._iomem, + "clock_divisor": self._clock_divisor} + + # update the router defaults with the platform specific defaults + router_defaults.update(platform_defaults) + return router_defaults + def list(self): """ Returns all c1700 instances @@ -65,7 +89,7 @@ class C1700(Router): def _setup_chassis(self): """ - Set up the router with the corresponding chassis + Sets up the router with the corresponding chassis (create slots and insert default adapters). """ @@ -91,7 +115,7 @@ class C1700(Router): @chassis.setter def chassis(self, chassis): """ - Set the chassis. + Sets the chassis. :param: chassis string: 1720, 1721, 1750, 1751 or 1760 @@ -99,6 +123,11 @@ class C1700(Router): self._hypervisor.send("c1700 set_chassis {name} {chassis}".format(name=self._name, chassis=chassis)) + + log.info("router {name} [id={id}]: chassis set to {chassis}".format(name=self._name, + id=self._id, + chassis=chassis)) + self._chassis = chassis self._setup_chassis() @@ -115,11 +144,16 @@ class C1700(Router): @iomem.setter def iomem(self, iomem): """ - Set I/O memory size for this router. + Sets I/O memory size for this router. :param iomem: I/O memory size """ self._hypervisor.send("c1700 set_iomem {name} {size}".format(name=self._name, size=iomem)) + + log.info("router {name} [id={id}]: I/O memory updated from {old_iomem}% to {new_iomem}%".format(name=self._name, + id=self._id, + old_iomem=self._iomem, + new_iomem=iomem)) self._iomem = iomem diff --git a/gns3server/modules/dynamips/nodes/c2600.py b/gns3server/modules/dynamips/nodes/c2600.py index eaf75cde..35a6fc4c 100644 --- a/gns3server/modules/dynamips/nodes/c2600.py +++ b/gns3server/modules/dynamips/nodes/c2600.py @@ -27,6 +27,9 @@ from ..adapters.c2600_mb_2e import C2600_MB_2E from ..adapters.c2600_mb_1fe import C2600_MB_1FE from ..adapters.c2600_mb_2fe import C2600_MB_2FE +import logging +log = logging.getLogger(__name__) + class C2600(Router): """ @@ -52,7 +55,7 @@ class C2600(Router): '2650XM': C2600_MB_1FE, '2651XM': C2600_MB_2FE} - def __init__(self, hypervisor, name, chassis="2610"): + def __init__(self, hypervisor, name=None, chassis="2610"): Router.__init__(self, hypervisor, name, platform="c2600") # Set default values for this platform @@ -69,6 +72,27 @@ class C2600(Router): self._setup_chassis() + def defaults(self): + """ + Returns all the default attribute values for this platform. + + :returns: default values (dictionary) + """ + + router_defaults = Router.defaults(self) + + platform_defaults = {"ram": self._ram, + "nvram": self._nvram, + "disk0": self._disk0, + "disk1": self._disk1, + "iomem": self._iomem, + "chassis": self._chassis, + "clock_divisor": self._clock_divisor} + + # update the router defaults with the platform specific defaults + router_defaults.update(platform_defaults) + return router_defaults + def list(self): """ Returns all c2600 instances @@ -80,7 +104,7 @@ class C2600(Router): def _setup_chassis(self): """ - Set up the router with the corresponding chassis + Sets up the router with the corresponding chassis (create slots and insert default adapters). """ @@ -100,7 +124,7 @@ class C2600(Router): @chassis.setter def chassis(self, chassis): """ - Set the chassis. + Sets the chassis. :param: chassis string: 2610, 2611, 2620, 2621, 2610XM, 2611XM @@ -109,6 +133,10 @@ class C2600(Router): self._hypervisor.send("c2600 set_chassis {name} {chassis}".format(name=self._name, chassis=chassis)) + + log.info("router {name} [id={id}]: chassis set to {chassis}".format(name=self._name, + id=self._id, + chassis=chassis)) self._chassis = chassis self._setup_chassis() @@ -125,11 +153,16 @@ class C2600(Router): @iomem.setter def iomem(self, iomem): """ - Set I/O memory size for this router. + Sets I/O memory size for this router. :param iomem: I/O memory size """ self._hypervisor.send("c2600 set_iomem {name} {size}".format(name=self._name, size=iomem)) + + log.info("router {name} [id={id}]: I/O memory updated from {old_iomem}% to {new_iomem}%".format(name=self._name, + id=self._id, + old_iomem=self._iomem, + new_iomem=iomem)) self._iomem = iomem diff --git a/gns3server/modules/dynamips/nodes/c2691.py b/gns3server/modules/dynamips/nodes/c2691.py index d075bbd5..75944c8e 100644 --- a/gns3server/modules/dynamips/nodes/c2691.py +++ b/gns3server/modules/dynamips/nodes/c2691.py @@ -24,6 +24,9 @@ from __future__ import unicode_literals from .router import Router from ..adapters.gt96100_fe import GT96100_FE +import logging +log = logging.getLogger(__name__) + class C2691(Router): """ @@ -33,7 +36,7 @@ class C2691(Router): :param name: name for this router """ - def __init__(self, hypervisor, name): + def __init__(self, hypervisor, name=None): Router.__init__(self, hypervisor, name, platform="c2691") # Set default values for this platform @@ -47,6 +50,26 @@ class C2691(Router): self._create_slots(2) self._slots[0] = GT96100_FE() + def defaults(self): + """ + Returns all the default attribute values for this platform. + + :returns: default values (dictionary) + """ + + router_defaults = Router.defaults(self) + + platform_defaults = {"ram": self._ram, + "nvram": self._nvram, + "disk0": self._disk0, + "disk1": self._disk1, + "iomem": self._iomem, + "clock_divisor": self._clock_divisor} + + # update the router defaults with the platform specific defaults + router_defaults.update(platform_defaults) + return router_defaults + def list(self): """ Returns all c2691 instances @@ -69,11 +92,16 @@ class C2691(Router): @iomem.setter def iomem(self, iomem): """ - Set I/O memory size for this router. + Sets I/O memory size for this router. :param iomem: I/O memory size """ self._hypervisor.send("c2691 set_iomem {name} {size}".format(name=self._name, size=iomem)) + + log.info("router {name} [id={id}]: I/O memory updated from {old_iomem}% to {new_iomem}%".format(name=self._name, + id=self._id, + old_iomem=self._iomem, + new_iomem=iomem)) self._iomem = iomem diff --git a/gns3server/modules/dynamips/nodes/c3600.py b/gns3server/modules/dynamips/nodes/c3600.py index 7806da54..e115e507 100644 --- a/gns3server/modules/dynamips/nodes/c3600.py +++ b/gns3server/modules/dynamips/nodes/c3600.py @@ -24,6 +24,9 @@ from __future__ import unicode_literals from .router import Router from ..adapters.leopard_2fe import Leopard_2FE +import logging +log = logging.getLogger(__name__) + class C3600(Router): """ @@ -35,7 +38,7 @@ class C3600(Router): 3620, 3640 or 3660 (default = 3640). """ - def __init__(self, hypervisor, name, chassis="3640"): + def __init__(self, hypervisor, name=None, chassis="3640"): Router.__init__(self, hypervisor, name, platform="c3600") # Set default values for this platform @@ -52,6 +55,27 @@ class C3600(Router): self._setup_chassis() + def defaults(self): + """ + Returns all the default attribute values for this platform. + + :returns: default values (dictionary) + """ + + router_defaults = Router.defaults(self) + + platform_defaults = {"ram": self._ram, + "nvram": self._nvram, + "disk0": self._disk0, + "disk1": self._disk1, + "iomem": self._iomem, + "chassis": self._chassis, + "clock_divisor": self._clock_divisor} + + # update the router defaults with the platform specific defaults + router_defaults.update(platform_defaults) + return router_defaults + def list(self): """ Returns all c3600 instances @@ -63,7 +87,7 @@ class C3600(Router): def _setup_chassis(self): """ - Set up the router with the corresponding chassis + Sets up the router with the corresponding chassis (create slots and insert default adapters). """ @@ -88,13 +112,18 @@ class C3600(Router): @chassis.setter def chassis(self, chassis): """ - Set the chassis. + Sets the chassis. :param: chassis string: 3620, 3640 or 3660 """ self._hypervisor.send("c3600 set_chassis {name} {chassis}".format(name=self._name, chassis=chassis)) + + log.info("router {name} [id={id}]: chassis set to {chassis}".format(name=self._name, + id=self._id, + chassis=chassis)) + self._chassis = chassis self._setup_chassis() @@ -118,4 +147,9 @@ class C3600(Router): self._hypervisor.send("c3600 set_iomem {name} {size}".format(name=self._name, size=iomem)) + + log.info("router {name} [id={id}]: I/O memory updated from {old_iomem}% to {new_iomem}%".format(name=self._name, + id=self._id, + old_iomem=self._iomem, + new_iomem=iomem)) self._iomem = iomem diff --git a/gns3server/modules/dynamips/nodes/c3725.py b/gns3server/modules/dynamips/nodes/c3725.py index 1730b48d..84bfa7dc 100644 --- a/gns3server/modules/dynamips/nodes/c3725.py +++ b/gns3server/modules/dynamips/nodes/c3725.py @@ -24,6 +24,9 @@ from __future__ import unicode_literals from .router import Router from ..adapters.gt96100_fe import GT96100_FE +import logging +log = logging.getLogger(__name__) + class C3725(Router): """ @@ -33,7 +36,7 @@ class C3725(Router): :param name: name for this router """ - def __init__(self, hypervisor, name): + def __init__(self, hypervisor, name=None): Router.__init__(self, hypervisor, name, platform="c3725") # Set default values for this platform @@ -47,6 +50,26 @@ class C3725(Router): self._create_slots(3) self._slots[0] = GT96100_FE() + def defaults(self): + """ + Returns all the default attribute values for this platform. + + :returns: default values (dictionary) + """ + + router_defaults = Router.defaults(self) + + platform_defaults = {"ram": self._ram, + "nvram": self._nvram, + "disk0": self._disk0, + "disk1": self._disk1, + "iomem": self._iomem, + "clock_divisor": self._clock_divisor} + + # update the router defaults with the platform specific defaults + router_defaults.update(platform_defaults) + return router_defaults + def list(self): """ Returns all c3725 instances. @@ -69,11 +92,16 @@ class C3725(Router): @iomem.setter def iomem(self, iomem): """ - Set I/O memory size for this router. + Sets I/O memory size for this router. :param iomem: I/O memory size """ self._hypervisor.send("c3725 set_iomem {name} {size}".format(name=self._name, size=iomem)) + + log.info("router {name} [id={id}]: I/O memory updated from {old_iomem}% to {new_iomem}%".format(name=self._name, + id=self._id, + old_iomem=self._iomem, + new_iomem=iomem)) self._iomem = iomem diff --git a/gns3server/modules/dynamips/nodes/c3745.py b/gns3server/modules/dynamips/nodes/c3745.py index 1ed94a40..aab03208 100644 --- a/gns3server/modules/dynamips/nodes/c3745.py +++ b/gns3server/modules/dynamips/nodes/c3745.py @@ -24,6 +24,9 @@ from __future__ import unicode_literals from .router import Router from ..adapters.gt96100_fe import GT96100_FE +import logging +log = logging.getLogger(__name__) + class C3745(Router): """ @@ -33,7 +36,7 @@ class C3745(Router): :param name: name for this router """ - def __init__(self, hypervisor, name): + def __init__(self, hypervisor, name=None): Router.__init__(self, hypervisor, name, platform="c3745") # Set default values for this platform @@ -47,6 +50,26 @@ class C3745(Router): self._create_slots(5) self._slots[0] = GT96100_FE() + def defaults(self): + """ + Returns all the default attribute values for this platform. + + :returns: default values (dictionary) + """ + + router_defaults = Router.defaults(self) + + platform_defaults = {"ram": self._ram, + "nvram": self._nvram, + "disk0": self._disk0, + "disk1": self._disk1, + "iomem": self._iomem, + "clock_divisor": self._clock_divisor} + + # update the router defaults with the platform specific defaults + router_defaults.update(platform_defaults) + return router_defaults + def list(self): """ Returns all c3745 instances. @@ -69,11 +92,16 @@ class C3745(Router): @iomem.setter def iomem(self, iomem): """ - Set I/O memory size for this router. + Sets I/O memory size for this router. :param iomem: I/O memory size """ self._hypervisor.send("c3745 set_iomem {name} {size}".format(name=self._name, size=iomem)) + + log.info("router {name} [id={id}]: I/O memory updated from {old_iomem}% to {new_iomem}%".format(name=self._name, + id=self._id, + old_iomem=self._iomem, + new_iomem=iomem)) self._iomem = iomem diff --git a/gns3server/modules/dynamips/nodes/c7200.py b/gns3server/modules/dynamips/nodes/c7200.py index baa981c6..47b54999 100644 --- a/gns3server/modules/dynamips/nodes/c7200.py +++ b/gns3server/modules/dynamips/nodes/c7200.py @@ -20,13 +20,15 @@ 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 from ..adapters.c7200_io_ge_e import C7200_IO_GE_E +import logging +log = logging.getLogger(__name__) + class C7200(Router): """ @@ -37,7 +39,7 @@ class C7200(Router): :param npe: default NPE """ - def __init__(self, hypervisor, name, npe="npe-400"): + def __init__(self, hypervisor, name=None, npe="npe-400"): Router.__init__(self, hypervisor, name, platform="c7200") # Set default values for this platform @@ -70,6 +72,27 @@ class C7200(Router): else: self._slots[0] = C7200_IO_2FE() + def defaults(self): + """ + Returns all the default attribute values for this platform. + + :returns: default values (dictionary) + """ + + router_defaults = Router.defaults(self) + + platform_defaults = {"ram": self._ram, + "nvram": self._nvram, + "disk0": self._disk0, + "disk1": self._disk1, + "npe": self._npe, + "midplane": self._midplane, + "clock_divisor": self._clock_divisor} + + # update the router defaults with the platform specific defaults + router_defaults.update(platform_defaults) + return router_defaults + def list(self): """ Returns all c7200 instances. @@ -92,7 +115,7 @@ class C7200(Router): @npe.setter def npe(self, npe): """ - Set the NPE model. + Sets the NPE model. :params npe: NPE model string (e.g. "npe-200") NPE models are npe-100, npe-150, npe-175, npe-200, @@ -104,6 +127,11 @@ class C7200(Router): self._hypervisor.send("c7200 set_npe {name} {npe}".format(name=self._name, npe=npe)) + + log.info("router {name} [id={id}]: NPE updated from {old_npe} to {new_npe}".format(name=self._name, + id=self._id, + old_npe=self._npe, + new_npe=npe)) self._npe = npe @property @@ -119,13 +147,18 @@ class C7200(Router): @midplane.setter def midplane(self, midplane): """ - Set the midplane model. + Sets the midplane model. :returns: midplane model string (e.g. "vxr" or "std") """ self._hypervisor.send("c7200 set_midplane {name} {midplane}".format(name=self._name, midplane=midplane)) + + log.info("router {name} [id={id}]: midplane updated from {old_midplane} to {new_midplane}".format(name=self._name, + id=self._id, + old_midplane=self._midplane, + new_midplane=midplane)) self._midplane = midplane @property @@ -141,7 +174,7 @@ class C7200(Router): @sensors.setter def sensors(self, sensors): """ - Set the 4 sensors with temperature in degree Celcius. + Sets the 4 sensors with temperature in degree Celcius. :param sensors: list of 4 sensor temperatures corresponding to sensor 1 = I/0 controller inlet @@ -156,6 +189,13 @@ class C7200(Router): self._hypervisor.send("c7200 set_temp_sensor {name} {sensor_id} {temp}".format(name=self._name, sensor_id=sensor_id, temp=sensor)) + + log.info("router {name} [id={id}]: sensor {sensor_id} temperature updated from {old_temp}C to {new_temp}C".format(name=self._name, + id=self._id, + sensor_id=sensor_id, + old_temp=self._sensors[sensor_id], + new_temp=sensors[sensor_id])) + sensor_id += 1 self._sensors = sensors @@ -172,7 +212,7 @@ class C7200(Router): @power_supplies.setter def power_supplies(self, power_supplies): """ - Set the 2 power supplies with 0 = off, 1 = on. + Sets the 2 power supplies with 0 = off, 1 = on. :param power_supplies: list of 2 power supplies. Example: [1, 0] = first power supply is on, second is off. @@ -183,5 +223,11 @@ class C7200(Router): self._hypervisor.send("c7200 set_power_supply {name} {power_supply_id} {powered_on}".format(name=self._name, power_supply_id=power_supply_id, powered_on=power_supply)) + + log.info("router {name} [id={id}]: power supply {power_supply_id} state updated to {powered_on}".format(name=self._name, + id=self._id, + power_supply_id=power_supply_id, + powered_on=power_supply)) power_supply_id += 1 + self._power_supplies = power_supplies diff --git a/gns3server/modules/dynamips/nodes/ethernet_switch.py b/gns3server/modules/dynamips/nodes/ethernet_switch.py index b0bde746..4f135b63 100644 --- a/gns3server/modules/dynamips/nodes/ethernet_switch.py +++ b/gns3server/modules/dynamips/nodes/ethernet_switch.py @@ -24,6 +24,9 @@ http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L558 from __future__ import unicode_literals from ..dynamips_error import DynamipsError +import logging +log = logging.getLogger(__name__) + class EthernetSwitch(object): """ @@ -33,15 +36,47 @@ class EthernetSwitch(object): :param name: name for this switch """ - def __init__(self, hypervisor, name): + _instance_count = 1 + + def __init__(self, hypervisor, name=None): + + # create an unique ID + self._id = EthernetSwitch._instance_count + EthernetSwitch._instance_count += 1 + + # let's create a unique name if none has been chosen + if not name: + name = "SW" + str(self._id) self._hypervisor = hypervisor self._name = '"' + name + '"' # put name into quotes to protect spaces self._hypervisor.send("ethsw create {}".format(self._name)) + + log.info("Ethernet switch {name} [id={id}] has been created".format(name=self._name, + id=self._id)) + self._hypervisor.devices.append(self) self._nios = {} self._mapping = {} + @classmethod + def reset(cls): + """ + Reset the instance count. + """ + + cls._instance_count = 1 + + @property + def id(self): + """ + Returns the unique ID for this Ethernet switch. + + :returns: id (integer) + """ + + return self._id + @property def name(self): """ @@ -101,6 +136,11 @@ class EthernetSwitch(object): new_name = '"' + new_name + '"' # put the new name into quotes to protect spaces self._hypervisor.send("ethsw rename {name} {new_name}".format(name=self._name, new_name=new_name)) + + log.info("Ethernet switch {name} [id={id}]: renamed to {new_name}".format(name=self._name, + id=self._id, + new_name=new_name)) + self._name = new_name def delete(self): @@ -109,6 +149,9 @@ class EthernetSwitch(object): """ self._hypervisor.send("ethsw delete {}".format(self._name)) + + log.info("Ethernet switch {name} [id={id}] has been deleted".format(name=self._name, + id=self._id)) self._hypervisor.devices.remove(self) def add_nio(self, nio, port): @@ -124,6 +167,11 @@ class EthernetSwitch(object): self._hypervisor.send("ethsw add_nio {name} {nio}".format(name=self._name, nio=nio)) + + log.info("Ethernet switch {name} [id={id}]: NIO {nio} bound to port {port}".format(name=self._name, + id=self._id, + nio=nio, + port=port)) self._nios[port] = nio def remove_nio(self, port): @@ -131,6 +179,8 @@ class EthernetSwitch(object): Removes the specified NIO as member of this Ethernet switch. :param port: allocated port + + :returns: the NIO that was bound to the port """ if port not in self._nios: @@ -139,14 +189,22 @@ class EthernetSwitch(object): nio = self._nios[port] self._hypervisor.send("ethsw remove_nio {name} {nio}".format(name=self._name, nio=nio)) + + log.info("Ethernet switch {name} [id={id}]: NIO {nio} removed from port {port}".format(name=self._name, + id=self._id, + nio=nio, + port=port)) + del self._nios[port] if port in self._mapping: del self._mapping[port] + return nio + def set_access_port(self, port, vlan_id): """ - Set the specified port as an ACCESS port. + Sets the specified port as an ACCESS port. :param port: allocated port :param vlan_id: VLAN number membership @@ -159,11 +217,16 @@ class EthernetSwitch(object): self._hypervisor.send("ethsw set_access_port {name} {nio} {vlan_id}".format(name=self._name, nio=nio, vlan_id=vlan_id)) + + log.info("Ethernet switch {name} [id={id}]: port {port} set as an access port in VLAN {vlan_id}".format(name=self._name, + id=self._id, + port=port, + vlan_id=vlan_id)) self._mapping[port] = ("access", vlan_id) def set_dot1q_port(self, port, native_vlan): """ - Set the specified port as a 802.1Q trunk port. + Sets the specified port as a 802.1Q trunk port. :param port: allocated port :param native_vlan: native VLAN for this trunk port @@ -176,11 +239,17 @@ class EthernetSwitch(object): self._hypervisor.send("ethsw set_dot1q_port {name} {nio} {native_vlan}".format(name=self._name, nio=nio, native_vlan=native_vlan)) + + log.info("Ethernet switch {name} [id={id}]: port {port} set as a 802.1Q port with native VLAN {vlan_id}".format(name=self._name, + id=self._id, + port=port, + vlan_id=native_vlan)) + self._mapping[port] = ("dot1q", native_vlan) def set_qinq_port(self, port, outer_vlan): """ - Set the specified port as a trunk (QinQ) port. + Sets the specified port as a trunk (QinQ) port. :param port: allocated port :param outer_vlan: outer VLAN (transport VLAN) for this QinQ port @@ -193,6 +262,11 @@ class EthernetSwitch(object): self._hypervisor.send("ethsw set_qinq_port {name} {nio} {outer_vlan}".format(name=self._name, nio=nio, outer_vlan=outer_vlan)) + + log.info("Ethernet switch {name} [id={id}]: port {port} set as a QinQ port with outer VLAN {vlan_id}".format(name=self._name, + id=self._id, + port=port, + vlan_id=outer_vlan)) self._mapping[port] = ("qinq", outer_vlan) def get_mac_addr_table(self): diff --git a/gns3server/modules/dynamips/nodes/frame_relay_switch.py b/gns3server/modules/dynamips/nodes/frame_relay_switch.py index d27f282a..c8e70633 100644 --- a/gns3server/modules/dynamips/nodes/frame_relay_switch.py +++ b/gns3server/modules/dynamips/nodes/frame_relay_switch.py @@ -23,6 +23,9 @@ http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L642 from __future__ import unicode_literals from ..dynamips_error import DynamipsError +import logging +log = logging.getLogger(__name__) + class FrameRelaySwitch(object): """ @@ -32,15 +35,47 @@ class FrameRelaySwitch(object): :param name: name for this switch """ - def __init__(self, hypervisor, name): + _instance_count = 1 + + def __init__(self, hypervisor, name=None): + + # create an unique ID + self._id = FrameRelaySwitch._instance_count + FrameRelaySwitch._instance_count += 1 + + # let's create a unique name if none has been chosen + if not name: + name = "FR" + str(self._id) self._hypervisor = hypervisor self._name = '"' + name + '"' # put name into quotes to protect spaces self._hypervisor.send("frsw create {}".format(self._name)) + + log.info("Frame Relay switch {name} [id={id}] has been created".format(name=self._name, + id=self._id)) + self._hypervisor.devices.append(self) self._nios = {} self._mapping = {} + @classmethod + def reset(cls): + """ + Reset the instance count. + """ + + cls._instance_count = 1 + + @property + def id(self): + """ + Returns the unique ID for this Frame Relay switch. + + :returns: id (integer) + """ + + return self._id + @property def name(self): """ @@ -100,6 +135,11 @@ class FrameRelaySwitch(object): new_name = '"' + new_name + '"' # put the new name into quotes to protect spaces self._hypervisor.send("frsw rename {name} {new_name}".format(name=self._name, new_name=new_name)) + + log.info("Frame Relay switch {name} [id={id}]: renamed to {new_name}".format(name=self._name, + id=self._id, + new_name=new_name)) + self._name = new_name def delete(self): @@ -108,8 +148,22 @@ class FrameRelaySwitch(object): """ self._hypervisor.send("frsw delete {}".format(self._name)) + + log.info("Frame Relay switch {name} [id={id}] has been deleted".format(name=self._name, + id=self._id)) self._hypervisor.devices.remove(self) + def has_port(self, port): + """ + Checks if a port exists on this Frame Relay switch. + + :returns: boolean + """ + + if port in self._nios: + return True + return False + def add_nio(self, nio, port): """ Adds a NIO as new port on Frame Relay switch. @@ -121,6 +175,11 @@ class FrameRelaySwitch(object): if port in self._nios: raise DynamipsError("Port {} isn't free".format(port)) + log.info("Frame Relay switch {name} [id={id}]: NIO {nio} bound to port {port}".format(name=self._name, + id=self._id, + nio=nio, + port=port)) + self._nios[port] = nio def remove_nio(self, port): @@ -128,12 +187,21 @@ class FrameRelaySwitch(object): Removes the specified NIO as member of this Frame Relay switch. :param port: allocated port + + :returns: the NIO that was bound to the allocated port """ if port not in self._nios: raise DynamipsError("Port {} is not allocated".format(port)) + nio = self._nios[port] + log.info("Frame Relay switch {name} [id={id}]: NIO {nio} removed from port {port}".format(name=self._name, + id=self._id, + nio=nio, + port=port)) + del self._nios[port] + return nio def map_vc(self, port1, dlci1, port2, dlci2): """ @@ -159,6 +227,14 @@ class FrameRelaySwitch(object): input_dlci=dlci1, output_nio=nio2, output_dlci=dlci2)) + + log.info("Frame Relay switch {name} [id={id}]: VC from port {port1} DLCI {dlci1} to port {port2} DLCI {dlci2} created".format(name=self._name, + id=self._id, + port1=port1, + dlci1=dlci1, + port2=port2, + dlci2=dlci2)) + self._mapping[(port1, dlci1)] = (port2, dlci2) def unmap_vc(self, port1, dlci1, port2, dlci2): @@ -185,4 +261,11 @@ class FrameRelaySwitch(object): input_dlci=dlci1, output_nio=nio2, output_dlci=dlci2)) + + log.info("Frame Relay switch {name} [id={id}]: VC from port {port1} DLCI {dlci1} to port {port2} DLCI {dlci2} deleted".format(name=self._name, + id=self._id, + port1=port1, + dlci1=dlci1, + port2=port2, + dlci2=dlci2)) del self._mapping[(port1, dlci1)] diff --git a/gns3server/modules/dynamips/nodes/hub.py b/gns3server/modules/dynamips/nodes/hub.py index ff3f8c41..b1e6d674 100644 --- a/gns3server/modules/dynamips/nodes/hub.py +++ b/gns3server/modules/dynamips/nodes/hub.py @@ -23,6 +23,9 @@ from __future__ import unicode_literals from .bridge import Bridge from ..dynamips_error import DynamipsError +import logging +log = logging.getLogger(__name__) + class Hub(Bridge): """ @@ -32,10 +35,41 @@ class Hub(Bridge): :param name: name for this hub """ + _instance_count = 1 + def __init__(self, hypervisor, name): - Bridge.__init__(self, hypervisor, name) + # create an unique ID + self._id = Hub._instance_count + Hub._instance_count += 1 + + # let's create a unique name if none has been chosen + if not name: + name = "Hub" + str(self._id) + self._mapping = {} + Bridge.__init__(self, hypervisor, name) + + log.info("Ethernet hub {name} [id={id}] has been created".format(name=self._name, + id=self._id)) + + @classmethod + def reset(cls): + """ + Reset the instance count. + """ + + cls._instance_count = 1 + + @property + def id(self): + """ + Returns the unique ID for this Ethernet switch. + + :returns: id (integer) + """ + + return self._id @property def mapping(self): @@ -47,6 +81,15 @@ class Hub(Bridge): return self._mapping + def delete(self): + """ + Deletes this hub. + """ + + Bridge.delete(self) + log.info("Ethernet hub {name} [id={id}] has been deleted".format(name=self._name, + id=self._id)) + def add_nio(self, nio, port): """ Adds a NIO as new port on this hub. @@ -59,6 +102,11 @@ class Hub(Bridge): raise DynamipsError("Port {} isn't free".format(port)) Bridge.add_nio(self, nio) + + log.info("Ethernet hub {name} [id={id}]: NIO {nio} bound to port {port}".format(name=self._name, + id=self._id, + nio=nio, + port=port)) self._mapping[port] = nio def remove_nio(self, port): @@ -66,6 +114,8 @@ class Hub(Bridge): Removes the specified NIO as member of this hub. :param port: allocated port + + :returns: the NIO that was bound to the allocated port """ if port not in self._mapping: @@ -73,4 +123,11 @@ class Hub(Bridge): nio = self._mapping[port] Bridge.remove_nio(self, nio) + + log.info("Ethernet switch {name} [id={id}]: NIO {nio} removed from port {port}".format(name=self._name, + id=self._id, + nio=nio, + port=port)) + del self._mapping[port] + return nio diff --git a/gns3server/modules/dynamips/nodes/router.py b/gns3server/modules/dynamips/nodes/router.py index 16259299..e1beaf38 100644 --- a/gns3server/modules/dynamips/nodes/router.py +++ b/gns3server/modules/dynamips/nodes/router.py @@ -24,29 +24,36 @@ from __future__ import unicode_literals from ..dynamips_error import DynamipsError import os +import logging +log = logging.getLogger(__name__) + class Router(object): """ - Dynamips router + Dynamips router implementation. :param hypervisor: Dynamips hypervisor object :param name: name for this router :param platform: c7200, c3745, c3725, c3600, c2691, c2600 or c1700 - :param console_flag: create console ports if True. + :param ghost_flag: used when creating a ghost IOS. """ - _instance_count = 0 + _instance_count = 1 _status = {0: "inactive", 1: "shutting down", 2: "running", 3: "suspended"} - def __init__(self, hypervisor, name, platform="c7200", console_flag=True): + def __init__(self, hypervisor, name=None, platform="c7200", ghost_flag=False): # create an unique ID self._id = Router._instance_count Router._instance_count += 1 + # let's create a unique name if none has been chosen + if not name: + name = "R" + str(self._id) + self._hypervisor = hypervisor self._name = '"' + name + '"' # put name into quotes to protect spaces self._platform = platform @@ -76,12 +83,66 @@ class Router(object): id=self._id, platform=self._platform)) - if console_flag: + if not ghost_flag: + 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 + else: + log.info("creating a new ghost IOS file") + Router._instance_count -= 1 self._hypervisor.devices.append(self) + @classmethod + def reset(cls): + """ + Reset the instance count. + """ + + cls._instance_count = 1 + + def defaults(self): + """ + Returns all the default base attribute values for routers. + + :returns: default values (dictionary) + """ + + router_defaults = {"platform": self._platform, + "image": self._image, + "ram": self._ram, + "nvram": self._nvram, + "mmap": self._mmap, + "sparsemem": self._sparsemem, + "clock_divisor": self._clock_divisor, + "idlepc": self._idlepc, + "idlemax": self._idlemax, + "idlesleep": self._idlesleep, + "exec_area": self._exec_area, + "jit_sharing_group": self._jit_sharing_group, + "disk0": self._disk0, + "disk1": self._disk1, + "confreg": self._confreg, + "console": self._console, + "aux": self._aux, + "mac_addr": self._mac_addr, + "system_id": self._system_id} + + slot_id = 0 + for slot in self._slots: + if slot: + slot = str(slot) + router_defaults["slot" + str(slot_id)] = slot + slot_id += 1 + + if self._slots[0] and self._slots[0].wics: + for wic_slot_id in range(0, len(self._slots[0].wics)): + router_defaults["wic" + str(wic_slot_id)] = None + + return router_defaults + @property def id(self): """ @@ -151,6 +212,11 @@ class Router(object): new_name = '"' + new_name + '"' # put the new name into quotes to protect spaces self._hypervisor.send("vm rename {name} {new_name}".format(name=self._name, new_name=new_name)) + + log.info("router {name} [id={id}]: renamed to {new_name}".format(name=self._name, + id=self._id, + new_name=new_name)) + self._name = new_name def delete(self): @@ -161,13 +227,19 @@ class Router(object): self._hypervisor.send("vm 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)) + def start(self): """ Starts this router. - At least the IOS image must be set. + At least the IOS image must be set before starting it. """ - self._hypervisor.send("vm start {}".format(self._name)) + if self.get_status() == "suspended": + self.resume() + else: + self._hypervisor.send("vm start {}".format(self._name)) + log.info("router {name} [id={id}] has been started".format(name=self._name, id=self._id)) def stop(self): """ @@ -176,13 +248,16 @@ class Router(object): """ self._hypervisor.send("vm stop {}".format(self._name)) + log.info("router {name} [id={id}] has been stopped".format(name=self._name, id=self._id)) def suspend(self): """ Suspends this router """ - self._hypervisor.send("vm suspend {}".format(self._name)) + if self.get_status() == "running": + self._hypervisor.send("vm suspend {}".format(self._name)) + log.info("router {name} [id={id}] has been suspended".format(name=self._name, id=self._id)) def resume(self): """ @@ -190,12 +265,13 @@ class Router(object): """ self._hypervisor.send("vm resume {}".format(self._name)) + log.info("router {name} [id={id}] has been resumed".format(name=self._name, id=self._id)) def get_status(self): """ Returns the status of this router - :returns: 0=inactive, 1=shutting down, 2=running, 3=suspended + :returns: inactive, shutting down, running or suspended. """ status_id = int(self._hypervisor.send("vm get_status {}".format(self._name))[0]) @@ -225,7 +301,7 @@ class Router(object): @jit_sharing_group.setter def jit_sharing_group(self, group_id): """ - Set the translation sharing group (unstable). + Sets the translation sharing group (unstable). :param group_id: translation sharing group ID """ @@ -236,12 +312,16 @@ class Router(object): self._hypervisor.send("vm set_tsg {name} {group_id}".format(name=self._name, group_id=group_id)) + log.info("router {name} [id={id}]: set in JIT sharing group {group_id}".format(name=self._name, + id=self._id, + group_id=group_id)) + self._jit_sharing_group = group_id self._hypervisor.add_jitsharing_group(os.path.basename(self._image), group_id) def set_debug_level(self, level): """ - Set the debug level for this router (default is 0). + Sets the debug level for this router (default is 0). :param level: level number """ @@ -262,7 +342,7 @@ class Router(object): @image.setter def image(self, image): """ - Set the IOS image for this router. + Sets the IOS image for this router. There is no default. :param image: path to IOS image file @@ -271,11 +351,16 @@ class Router(object): # encase image in quotes to protect spaces in the path self._hypervisor.send("vm set_ios {name} {image}".format(name=self._name, image='"' + image + '"')) + + log.info("router {name} [id={id}]: has a new IOS image set: {image}".format(name=self._name, + id=self._id, + image='"' + image + '"')) + self._image = image def set_config(self, startup_config, private_config=''): """ - Set the config files that are pushed to startup-config and + Sets the config files that are pushed to startup-config and private-config in NVRAM when the instance is started. :param startup_config: path to statup-config file @@ -287,6 +372,15 @@ class Router(object): startup='"' + startup_config + '"', private='"' + private_config + '"')) + log.info("router {name} [id={id}]: has a startup-config set: {startup}".format(name=self._name, + id=self._id, + 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): """ Gets the contents of the config files @@ -318,6 +412,13 @@ class Router(object): startup=startup_config, private=private_config)) + log.info("router {name} [id={id}]: new startup-config pushed".format(name=self._name, + id=self._id)) + + if private_config != '(keep)': + log.info("router {name} [id={id}]: new private-config pushed".format(name=self._name, + id=self._id)) + @property def ram(self): """ @@ -331,13 +432,22 @@ class Router(object): @ram.setter def ram(self, ram): """ - Set amount of RAM allocated to this router + Sets amount of RAM allocated to this router :param ram: amount of RAM in Mbytes (integer) """ + if self._ram == ram: + return + self._hypervisor.send("vm set_ram {name} {ram}".format(name=self._name, - ram=self._ram)) + ram=ram)) + + log.info("router {name} [id={id}]: RAM updated from {old_ram}MB to {new_ram}MB".format(name=self._name, + id=self._id, + old_ram=self._ram, + new_ram=ram)) + self._hypervisor.decrease_memory_load(self._ram) self._ram = ram self._hypervisor.increase_memory_load(self._ram) @@ -355,13 +465,21 @@ class Router(object): @nvram.setter def nvram(self, nvram): """ - Set amount of NVRAM allocated to this router + Sets amount of NVRAM allocated to this router :param nvram: amount of NVRAM in Kbytes (integer) """ + if self._nvram == nvram: + return + self._hypervisor.send("vm set_nvram {name} {nvram}".format(name=self._name, - nvram=self._nvram)) + nvram=nvram)) + + log.info("router {name} [id={id}]: NVRAM updated from {old_nvram}KB to {new_nvram}KB".format(name=self._name, + id=self._id, + old_nvram=self._nvram, + new_nvram=nvram)) self._nvram = nvram @property @@ -389,6 +507,13 @@ class Router(object): flag = 0 self._hypervisor.send("vm set_ram_mmap {name} {mmap}".format(name=self._name, mmap=flag)) + + if mmap: + log.info("router {name} [id={id}]: mmap enabled".format(name=self._name, + id=self._id)) + else: + log.info("router {name} [id={id}]: mmap disabled".format(name=self._name, + id=self._id)) self._mmap = mmap @property @@ -415,6 +540,13 @@ class Router(object): flag = 0 self._hypervisor.send("vm set_sparse_mem {name} {sparsemem}".format(name=self._name, sparsemem=flag)) + + if sparsemem: + log.info("router {name} [id={id}]: sparse memory enabled".format(name=self._name, + id=self._id)) + else: + log.info("router {name} [id={id}]: sparse memory disabled".format(name=self._name, + id=self._id)) self._sparsemem = sparsemem @property @@ -430,7 +562,7 @@ class Router(object): @clock_divisor.setter def clock_divisor(self, clock_divisor): """ - Set the clock divisor value. The higher is the value, the faster is the clock in the + Sets the clock divisor value. The higher is the value, the faster is the clock in the virtual machine. The default is 4, but it is often required to adjust it. :param clock_divisor: clock divisor value (integer) @@ -438,6 +570,11 @@ class Router(object): self._hypervisor.send("vm set_clock_divisor {name} {clock}".format(name=self._name, clock=clock_divisor)) + + log.info("router {name} [id={id}]: clock divisor updated from {old_clock} to {new_clock}".format(name=self._name, + id=self._id, + old_clock=self._clock_divisor, + new_clock=clock_divisor)) self._clock_divisor = clock_divisor @property @@ -453,7 +590,7 @@ class Router(object): @idlepc.setter def idlepc(self, idlepc): """ - Set the idle Pointer Counter (PC) + Sets the idle Pointer Counter (PC) :param idlepc: idlepc value (string) """ @@ -465,6 +602,11 @@ class Router(object): else: self._hypervisor.send("vm set_idle_pc_online {name} 0 {idlepc}".format(name=self._name, idlepc=idlepc)) + + log.info("router {name} [id={id}]: idle-PC set to {idlepc}".format(name=self._name, + id=self._id, + idlepc=idlepc)) + self._idlepc = idlepc def get_idle_pc_prop(self): @@ -500,7 +642,7 @@ class Router(object): @idlemax.setter def idlemax(self, idlemax): """ - Set CPU idle max value + Sets CPU idle max value :param idlemax: idle max value (integer) """ @@ -508,6 +650,12 @@ class Router(object): if self.is_running(): # router is running self._hypervisor.send("vm set_idle_max {name} 0 {idlemax}".format(name=self._name, idlemax=idlemax)) + + log.info("router {name} [id={id}]: idlemax updated from {old_idlemax} to {new_idlemax}".format(name=self._name, + id=self._id, + old_idlemax=self._idlemax, + new_idlemax=idlemax)) + self._idlemax = idlemax @property @@ -523,7 +671,7 @@ class Router(object): @idlesleep.setter def idlesleep(self, idlesleep): """ - Set CPU idle sleep time value. + Sets CPU idle sleep time value. :param idlesleep: idle sleep value (integer) """ @@ -531,6 +679,12 @@ class Router(object): if self.is_running(): # router is running self._hypervisor.send("vm set_idle_sleep_time {name} 0 {idlesleep}".format(name=self._name, idlesleep=idlesleep)) + + log.info("router {name} [id={id}]: idlesleep updated from {old_idlesleep} to {new_idlesleep}".format(name=self._name, + id=self._id, + old_idlesleep=self._idlesleep, + new_idlesleep=idlesleep)) + self._idlesleep = idlesleep def show_timer_drift(self): @@ -555,16 +709,21 @@ class Router(object): @ghost_file.setter def ghost_file(self, ghost_file): """ - Set ghost RAM file + Sets ghost RAM file :ghost_file: path to ghost file """ self._hypervisor.send("vm set_ghost_file {name} {ghost_file}".format(name=self._name, ghost_file=ghost_file)) + + log.info("router {name} [id={id}]: ghost file set to {ghost_file}".format(name=self._name, + id=self._id, + ghost_file=ghost_file)) + self._ghost_file = ghost_file - # If this is a ghost instance, track this as a hosted ghost instance by this hypervisor + # if this is a ghost instance, track this as a hosted ghost instance by this hypervisor if self.ghost_status == 1: self._hypervisor.add_ghost(ghost_file, self) @@ -575,8 +734,8 @@ class Router(object): :returns: formatted ghost_file name (string) """ - # Replace specials characters in 'drive:\filename' in Linux and Dynamips in MS Windows or viceversa. - ghost_file = os.path.basename(self._image) + '-' + self._hypervisor.host + '.ghost' + # replace specials characters in 'drive:\filename' in Linux and Dynamips in MS Windows or viceversa. + ghost_file = "{}-{}.ghost".format(os.path.basename(self._image), self._ram) ghost_file = ghost_file.replace('\\', '-').replace('/', '-').replace(':', '-') return ghost_file @@ -592,7 +751,7 @@ class Router(object): @ghost_status.setter def ghost_status(self, ghost_status): """ - Set ghost RAM status + Sets ghost RAM status :param ghost_status: state flag indicating status 0 => Do not use IOS ghosting @@ -602,6 +761,10 @@ class Router(object): self._hypervisor.send("vm set_ghost_status {name} {ghost_status}".format(name=self._name, ghost_status=ghost_status)) + + log.info("router {name} [id={id}]: ghost status set to {ghost_status}".format(name=self._name, + id=self._id, + ghost_status=ghost_status)) self._ghost_status = ghost_status @property @@ -617,7 +780,7 @@ class Router(object): @exec_area.setter def exec_area(self, exec_area): """ - Set the exec area value. + Sets the exec area value. The exec area is a pool of host memory used to store pages translated by the JIT (they contain the native code corresponding to MIPS code pages). @@ -627,6 +790,11 @@ class Router(object): self._hypervisor.send("vm set_exec_area {name} {exec_area}".format(name=self._name, exec_area=exec_area)) + + log.info("router {name} [id={id}]: exec area updated from {old_exec}MB to {new_exec}MB".format(name=self._name, + id=self._id, + old_exec=self._exec_area, + new_exec=exec_area)) self._exec_area = exec_area @property @@ -642,13 +810,18 @@ class Router(object): @disk0.setter def disk0(self, disk0): """ - Set the size (MB) for PCMCIA disk0. + Sets the size (MB) for PCMCIA disk0. :param disk0: disk0 size (integer) """ self._hypervisor.send("vm set_disk0 {name} {disk0}".format(name=self._name, disk0=disk0)) + + log.info("router {name} [id={id}]: disk0 updated from {old_disk0}MB to {new_disk0}MB".format(name=self._name, + id=self._id, + old_disk0=self._disk0, + new_disk0=disk0)) self._disk0 = disk0 @property @@ -664,13 +837,18 @@ class Router(object): @disk1.setter def disk1(self, disk1): """ - Set the size (MB) for PCMCIA disk1. + Sets the size (MB) for PCMCIA disk1. :param disk1: disk1 size (integer) """ self._hypervisor.send("vm set_disk1 {name} {disk1}".format(name=self._name, disk1=disk1)) + + log.info("router {name} [id={id}]: disk1 updated from {old_disk1}MB to {new_disk1}MB".format(name=self._name, + id=self._id, + old_disk1=self._disk1, + new_disk1=disk1)) self._disk1 = disk1 @property @@ -687,13 +865,18 @@ class Router(object): @confreg.setter def confreg(self, confreg): """ - Set the configuration register. + Sets the configuration register. :param confreg: configuration register value (string) """ self._hypervisor.send("vm set_conf_reg {name} {confreg}".format(name=self._name, confreg=confreg)) + + log.info("router {name} [id={id}]: confreg updated from {old_confreg} to {new_confreg}".format(name=self._name, + id=self._id, + old_confreg=self._confreg, + new_confreg=confreg)) self._confreg = confreg @property @@ -709,7 +892,7 @@ class Router(object): @console.setter def console(self, console): """ - Set the TCP console port. + Sets the TCP console port. :param console: console port (integer) """ @@ -719,6 +902,11 @@ class Router(object): self._hypervisor.send("vm set_con_tcp_port {name} {console}".format(name=self._name, console=console)) + + log.info("router {name} [id={id}]: console port updated from {old_console} to {new_console}".format(name=self._name, + id=self._id, + old_console=self._console, + new_console=console)) self._console = console @property @@ -734,7 +922,7 @@ class Router(object): @aux.setter def aux(self, aux): """ - Set the TCP auxiliary port. + Sets the TCP auxiliary port. :param aux: console auxiliary port (integer) """ @@ -744,6 +932,11 @@ class Router(object): self._hypervisor.send("vm set_aux_tcp_port {name} {aux}".format(name=self._name, aux=aux)) + + log.info("router {name} [id={id}]: aux port updated from {old_aux} to {new_aux}".format(name=self._name, + id=self._id, + old_aux=self._aux, + new_aux=aux)) self._aux = aux def get_cpu_info(self, cpu_id=0): @@ -801,7 +994,7 @@ class Router(object): @mac_addr.setter def mac_addr(self, mac_addr): """ - Set the MAC address. + Sets the MAC address. :param mac_addr: a MAC address (hexadecimal format: hh:hh:hh:hh:hh:hh) """ @@ -809,6 +1002,11 @@ class Router(object): self._hypervisor.send("{platform} set_mac_addr {name} {mac_addr}".format(platform=self._platform, name=self._name, mac_addr=mac_addr)) + + log.info("router {name} [id={id}]: MAC address updated from {old_mac} to {new_mac}".format(name=self._name, + id=self._id, + old_mac=self._mac_addr, + new_mac=mac_addr)) self._mac_addr = mac_addr @property @@ -824,7 +1022,7 @@ class Router(object): @system_id.setter def system_id(self, system_id): """ - Set the system ID. + Sets the system ID. :param system_id: a system ID (also called board processor ID) """ @@ -832,6 +1030,11 @@ class Router(object): self._hypervisor.send("{platform} set_system_id {name} {system_id}".format(platform=self._platform, name=self._name, system_id=system_id)) + + log.info("router {name} [id={id}]: system ID updated from {old_id} to {new_id}".format(name=self._name, + id=self._id, + old_id=self._system_id, + new_id=system_id)) self._system_id = system_id def get_hardware_info(self): @@ -841,7 +1044,7 @@ class Router(object): :returns: ? (could not test) """ - # FIXME: nothing returned by Dynamips. + # FIXME: nothing returned by Dynamips. return (self._hypervisor.send("{platform} show_hardware {name}".format(platform=self._platform, name=self._name))) @@ -856,7 +1059,7 @@ class Router(object): def slot_add_binding(self, slot_id, adapter): """ - Adds a slot binding. + Adds a slot binding (a module into a slot). :param slot_id: slot ID :param adapter: device to add in the corresponding slot (object) @@ -877,6 +1080,12 @@ class Router(object): self._hypervisor.send("vm slot_add_binding {name} {slot_id} 0 {adapter}".format(name=self._name, slot_id=slot_id, adapter=adapter)) + + log.info("router {name} [id={id}]: adapter {adapter} inserted into slot {slot_id}".format(name=self._name, + id=self._id, + adapter=adapter, + slot_id=slot_id)) + self._slots[slot_id] = adapter # Generate an OIR event if the router is running and @@ -888,33 +1097,48 @@ class Router(object): self._hypervisor.send("vm slot_oir_start {name} {slot_id} 0".format(name=self._name, slot_id=slot_id)) + log.info("router {name} [id={id}]: OIR start event sent to slot {slot_id}".format(name=self._name, + id=self._id, + slot_id=slot_id)) + def slot_remove_binding(self, slot_id): """ - Removes a slot binding. + Removes a slot binding (a module from a slot). :param slot_id: slot ID """ try: - slot = self._slots[slot_id] + adapter = self._slots[slot_id] except IndexError: raise DynamipsError("Slot {slot_id} doesn't exist on router {name}".format(name=self._name, slot_id=slot_id)) - if slot == None: + if adapter == None: return + #FIXME: check if adapter can be removed! + # Generate an OIR event if the router is running and # only for c7200, c3600 and c3745 (NM-4T only) if self.is_running() and self._platform == 'c7200' \ or (self._platform == 'c3600' and self.chassis == '3660') \ - or (self._platform == 'c3745' and slot == 'NM-4T'): + or (self._platform == 'c3745' and adapter == 'NM-4T'): self._hypervisor.send("vm slot_oir_stop {name} {slot_id} 0".format(name=self._name, slot_id=slot_id)) + log.info("router {name} [id={id}]: OIR stop event sent to slot {slot_id}".format(name=self._name, + id=self._id, + slot_id=slot_id)) + self._hypervisor.send("vm slot_remove_binding {name} {slot_id} 0".format(name=self._name, - slot_id=slot_id)) + slot_id=slot_id)) + + log.info("router {name} [id={id}]: adapter {adapter} removed from slot {slot_id}".format(name=self._name, + id=self._id, + adapter=adapter, + slot_id=slot_id)) self._slots[slot_id] = None def install_wic(self, wic_slot_id, wic): @@ -947,6 +1171,12 @@ class Router(object): slot_id=slot_id, wic_slot_id=internal_wic_slot_id, wic=wic)) + + log.info("router {name} [id={id}]: {wic} inserted into WIC slot {wic_slot_id}".format(name=self._name, + id=self._id, + wic=wic, + wic_slot_id=wic_slot_id)) + adapter.install_wic(wic_slot_id, wic) def uninstall_wic(self, wic_slot_id): @@ -976,6 +1206,11 @@ class Router(object): self._hypervisor.send("vm slot_remove_binding {name} {slot_id} {wic_slot_id}".format(name=self._name, slot_id=slot_id, wic_slot_id=internal_wic_slot_id)) + + log.info("router {name} [id={id}]: {wic} removed from WIC slot {wic_slot_id}".format(name=self._name, + id=self._id, + wic=adapter.wics[wic_slot_id], + wic_slot_id=wic_slot_id)) adapter.uninstall_wic(wic_slot_id) def get_slot_nio_bindings(self, slot_id): @@ -1012,6 +1247,13 @@ class Router(object): slot_id=slot_id, port_id=port_id, nio=nio)) + + log.info("router {name} [id={id}]: NIO {nio_name} bound to port {slot_id}/{port_id}".format(name=self._name, + id=self._id, + nio_name=nio.name, + slot_id=slot_id, + port_id=port_id)) + self.slot_enable_nio(slot_id, port_id) adapter.add_nio(port_id, nio) @@ -1021,6 +1263,8 @@ class Router(object): :param slot_id: slot ID :param port_id: port ID + + :returns: removed NIO object """ try: @@ -1033,12 +1277,21 @@ class Router(object): port_id=port_id)) self.slot_disable_nio(slot_id, port_id) - self._hypervisor.send("vm slot_remove_binding {name} {slot_id} {port_id}".format(name=self._name, - slot_id=slot_id, - port_id=port_id)) + self._hypervisor.send("vm slot_remove_nio_binding {name} {slot_id} {port_id}".format(name=self._name, + slot_id=slot_id, + port_id=port_id)) + nio = adapter.get_nio(port_id) adapter.remove_nio(port_id) + log.info("router {name} [id={id}]: NIO {nio_name} removed from port {slot_id}/{port_id}".format(name=self._name, + id=self._id, + nio_name=nio.name, + slot_id=slot_id, + port_id=port_id)) + + return nio + def slot_enable_nio(self, slot_id, port_id): """ Enables a slot NIO binding. @@ -1052,6 +1305,11 @@ class Router(object): slot_id=slot_id, port_id=port_id)) + log.info("router {name} [id={id}]: NIO enabled on port {slot_id}/{port_id}".format(name=self._name, + id=self._id, + slot_id=slot_id, + port_id=port_id)) + def slot_disable_nio(self, slot_id, port_id): """ Disables a slot NIO binding. @@ -1065,6 +1323,11 @@ class Router(object): slot_id=slot_id, port_id=port_id)) + log.info("router {name} [id={id}]: NIO disabled on port {slot_id}/{port_id}".format(name=self._name, + id=self._id, + slot_id=slot_id, + port_id=port_id)) + def _create_slots(self, numslots): """ Creates the appropriate number of slots for this router. diff --git a/gns3server/server.py b/gns3server/server.py index 00654f32..c90b085a 100644 --- a/gns3server/server.py +++ b/gns3server/server.py @@ -84,7 +84,7 @@ class Server(object): tornado_app = tornado.web.Application(self.handlers, debug=True) # FIXME: debug mode! try: print("Starting server on port {}".format(self._port)) - tornado_app.listen(self._port) + tornado_app.listen(self._port, address=self._host) except socket.error as e: if e.errno == errno.EADDRINUSE: # socket already in use logging.critical("socket in use for port {}".format(self._port)) @@ -92,7 +92,7 @@ class Server(object): ioloop = tornado.ioloop.IOLoop.instance() stream = zmqstream.ZMQStream(router, ioloop) - stream.on_recv(JSONRPCWebSocket.dispatch_message) + stream.on_recv_stream(JSONRPCWebSocket.dispatch_message) tornado.autoreload.add_reload_hook(functools.partial(self._cleanup, stop=False)) def signal_handler(signum=None, frame=None): diff --git a/gns3server/topology.py b/gns3server/topology.py deleted file mode 100644 index e14ce555..00000000 --- a/gns3server/topology.py +++ /dev/null @@ -1,49 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2013 GNS3 Technologies Inc. -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -#import networkx as nx - - -class Topology(object): - - def __init__(self): - - pass - #self._topology = nx.Graph() - - def add_node(self, node): - - self._topology.add_node(node) - - def remove_node(self, node): - - self._topology.remove_node(node) - - def clear(self): - - self._topology.clear() - - def __str__(self): - - return "GNS3 network topology" - - @staticmethod - def instance(): - - if not hasattr(Topology, "_instance"): - Topology._instance = Topology() - return Topology._instance diff --git a/requirements.txt b/requirements.txt index 9c49a8d2..e7638d60 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ tornado pyzmq +netifaces-py3 diff --git a/tests/test_jsonrpc.py b/tests/test_jsonrpc.py index a58d4266..67bea4a9 100644 --- a/tests/test_jsonrpc.py +++ b/tests/test_jsonrpc.py @@ -36,7 +36,7 @@ class JSONRPC(AsyncTestCase): def test_request_with_invalid_version(self): - request = {"jsonrpc": "1.0", "method": "dynamips.echo", "id": 1} + request = {"jsonrpc": 1.0, "method": "dynamips.echo", "id": 1} AsyncWSRequest(self.URL, self.io_loop, self.stop, json_encode(request)) response = self.wait() json_response = json_decode(response)