diff --git a/gns3server/handlers/dynamips_device_handler.py b/gns3server/handlers/dynamips_device_handler.py index 11c3392c..4be6be4c 100644 --- a/gns3server/handlers/dynamips_device_handler.py +++ b/gns3server/handlers/dynamips_device_handler.py @@ -220,7 +220,7 @@ class DynamipsDeviceHandler: 404: "Instance doesn't exist" }, description="Stop a packet capture on a Dynamips device instance") - def start_capture(request, response): + def stop_capture(request, response): dynamips_manager = Dynamips.instance() device = dynamips_manager.get_device(request.match_info["device_id"], project_id=request.match_info["project_id"]) diff --git a/gns3server/handlers/dynamips_vm_handler.py b/gns3server/handlers/dynamips_vm_handler.py index 44d9a707..f88c9297 100644 --- a/gns3server/handlers/dynamips_vm_handler.py +++ b/gns3server/handlers/dynamips_vm_handler.py @@ -57,17 +57,11 @@ class DynamipsVMHandler: request.json.get("dynamips_id"), request.json.pop("platform")) - # set VM settings - for name, value in request.json.items(): - if hasattr(vm, name) and getattr(vm, name) != value: - if hasattr(vm, "set_{}".format(name)): - setter = getattr(vm, "set_{}".format(name)) - if asyncio.iscoroutinefunction(vm.close): - yield from setter(value) - else: - setter(value) - + yield from dynamips_manager.update_vm_settings(vm, request.json) yield from dynamips_manager.ghost_ios_support(vm) + yield from dynamips_manager.create_vm_configs(vm, + request.json.get("startup_config_content"), + request.json.get("private_config_content")) response.set_status(201) response.json(vm) @@ -112,14 +106,7 @@ class DynamipsVMHandler: dynamips_manager = Dynamips.instance() vm = dynamips_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"]) - # set VM settings - for name, value in request.json.items(): - if hasattr(vm, name) and getattr(vm, name) != value: - setter = getattr(vm, "set_{}".format(name)) - if asyncio.iscoroutinefunction(vm.close): - yield from setter(value) - else: - setter(value) + yield from dynamips_manager.update_vm_settings(vm, request.json) response.json(vm) @classmethod @@ -333,7 +320,7 @@ class DynamipsVMHandler: 404: "Instance doesn't exist" }, description="Stop a packet capture on a Dynamips VM instance") - def start_capture(request, response): + def stop_capture(request, response): dynamips_manager = Dynamips.instance() vm = dynamips_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"]) diff --git a/gns3server/handlers/virtualbox_handler.py b/gns3server/handlers/virtualbox_handler.py index ae8d296e..51768bd3 100644 --- a/gns3server/handlers/virtualbox_handler.py +++ b/gns3server/handlers/virtualbox_handler.py @@ -336,7 +336,7 @@ class VirtualBoxHandler: 404: "Instance doesn't exist" }, description="Stop a packet capture on a VirtualBox VM instance") - def start_capture(request, response): + def stop_capture(request, response): vbox_manager = VirtualBox.instance() vm = vbox_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"]) diff --git a/gns3server/modules/dynamips/__init__.py b/gns3server/modules/dynamips/__init__.py index 579d8bc3..d76e77ee 100644 --- a/gns3server/modules/dynamips/__init__.py +++ b/gns3server/modules/dynamips/__init__.py @@ -56,6 +56,51 @@ from .nios.nio_fifo import NIOFIFO from .nios.nio_mcast import NIOMcast from .nios.nio_null import NIONull +# 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 +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 + + +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} + class Dynamips(BaseManager): @@ -391,67 +436,93 @@ class Dynamips(BaseManager): # set the ghost file to the router yield from vm.set_ghost_status(2) yield from vm.set_ghost_file(ghost_file) -# -# def create_config_from_file(self, local_base_config, router, destination_config_path): -# """ -# Creates a config file from a local base config -# -# :param local_base_config: path the a local base config -# :param router: router instance -# :param destination_config_path: path to the destination config file -# -# :returns: relative path to the created config file -# """ -# -# log.info("creating config file {} from {}".format(destination_config_path, local_base_config)) -# config_path = destination_config_path -# config_dir = os.path.dirname(destination_config_path) -# try: -# os.makedirs(config_dir) -# except FileExistsError: -# pass -# except OSError as e: -# raise DynamipsError("Could not create configs directory: {}".format(e)) -# -# try: -# with open(local_base_config, "r", errors="replace") as f: -# config = f.read() -# with open(config_path, "w") as f: -# config = "!\n" + config.replace("\r", "") -# config = config.replace('%h', router.name) -# f.write(config) -# except OSError as e: -# raise DynamipsError("Could not save the configuration from {} to {}: {}".format(local_base_config, config_path, e)) -# return "configs" + os.sep + os.path.basename(config_path) -# -# def create_config_from_base64(self, config_base64, router, destination_config_path): -# """ -# Creates a config file from a base64 encoded config. -# -# :param config_base64: base64 encoded config -# :param router: router instance -# :param destination_config_path: path to the destination config file -# -# :returns: relative path to the created config file -# """ -# -# log.info("creating config file {} from base64".format(destination_config_path)) -# config = base64.decodebytes(config_base64.encode("utf-8")).decode("utf-8") -# config = "!\n" + config.replace("\r", "") -# config = config.replace('%h', router.name) -# config_dir = os.path.dirname(destination_config_path) -# try: -# os.makedirs(config_dir) -# except FileExistsError: -# pass -# except OSError as e: -# raise DynamipsError("Could not create configs directory: {}".format(e)) -# -# config_path = destination_config_path -# try: -# with open(config_path, "w") as f: -# log.info("saving startup-config to {}".format(config_path)) -# f.write(config) -# except OSError as e: -# raise DynamipsError("Could not save the configuration {}: {}".format(config_path, e)) -# return "configs" + os.sep + os.path.basename(config_path) + + @asyncio.coroutine + def update_vm_settings(self, vm, settings): + """ + Updates the VM settings. + + :param vm: VM instance + :param settings: settings to update (dict) + """ + + for name, value in settings.items(): + if hasattr(vm, name) and getattr(vm, name) != value: + if hasattr(vm, "set_{}".format(name)): + setter = getattr(vm, "set_{}".format(name)) + if asyncio.iscoroutinefunction(vm.close): + yield from setter(value) + else: + setter(value) + elif name.startswith("slot") and value in ADAPTER_MATRIX: + slot_id = int(name[-1]) + adapter_name = value + adapter = ADAPTER_MATRIX[adapter_name]() + if vm.slots[slot_id] and type(vm.slots[slot_id]) != type(adapter): + yield from vm.slot_remove_binding(slot_id) + yield from vm.slot_add_binding(slot_id, adapter) + elif name.startswith("slot") and value is None: + slot_id = int(name[-1]) + if vm.slots[slot_id]: + yield from vm.slot_remove_binding(slot_id) + elif name.startswith("wic") and value in WIC_MATRIX: + wic_slot_id = int(name[-1]) + wic_name = value + wic = WIC_MATRIX[wic_name]() + if vm.slots[0].wics[wic_slot_id] and type(vm.slots[0].wics[wic_slot_id]) != type(wic): + yield from vm.uninstall_wic(wic_slot_id) + yield from vm.install_wic(wic_slot_id, wic) + elif name.startswith("wic") and value is None: + wic_slot_id = int(name[-1]) + if vm.slots[0].wics and vm.slots[0].wics[wic_slot_id]: + yield from vm.uninstall_wic(wic_slot_id) + + @asyncio.coroutine + def create_vm_configs(self, vm, startup_config_content, private_config_content): + """ + Creates VM configs from pushed content. + + :param vm: VM instance + :param startup_config_content: content of the startup-config + :param private_config_content: content of the private-config + """ + + default_startup_config_path = os.path.join(vm.project.vm_working_directory(vm), "configs", "i{}_startup-config.cfg".format(vm.dynamips_id)) + default_private_config_path = os.path.join(vm.project.vm_working_directory(vm), "configs", "i{}_private-config.cfg".format(vm.dynamips_id)) + + if startup_config_content: + startup_config_path = self._create_config(vm, startup_config_content, default_startup_config_path) + yield from vm.set_config(startup_config_path) + + if private_config_content: + private_config_path = self._create_config(vm, private_config_content, default_private_config_path) + yield from vm.set_config(vm.startup_config, private_config_path) + + def _create_config(self, vm, content, path): + """ + Creates a config file. + + :param vm: VM instance + :param content: config content + :param path: path to the destination config file + + :returns: relative path to the created config file + """ + + log.info("Creating config file {}".format(path)) + content = "!\n" + content.replace("\r", "") + content = content.replace('%h', vm.name) + config_dir = os.path.dirname(path) + try: + os.makedirs(config_dir, exist_ok=True) + except OSError as e: + raise DynamipsError("Could not create Dynamips configs directory: {}".format(e)) + + try: + with open(path, "w") as f: + log.info("Creating config file {}".format(path)) + f.write(content) + except OSError as e: + raise DynamipsError("Could not create config file {}: {}".format(path, e)) + + return os.path.join("configs", os.path.basename(path)) diff --git a/gns3server/modules/dynamips/nodes/router.py b/gns3server/modules/dynamips/nodes/router.py index 31c124a7..4f4cb4b9 100644 --- a/gns3server/modules/dynamips/nodes/router.py +++ b/gns3server/modules/dynamips/nodes/router.py @@ -25,6 +25,7 @@ import time import sys import os import glob +import base64 import logging log = logging.getLogger(__name__) @@ -146,17 +147,19 @@ class Router(BaseVM): "mac_addr": self._mac_addr, "system_id": self._system_id} - # FIXME: add default slots/wics - # slot_number = 0 - # for slot in self._slots: - # if slot: - # slot = str(slot) - # router_defaults["slot" + str(slot_number)] = slot - # slot_number += 1 + # add the slots + slot_number = 0 + for slot in self._slots: + if slot: + slot = str(slot) + router_info["slot" + str(slot_number)] = slot + slot_number += 1 - # if self._slots[0] and self._slots[0].wics: - # for wic_slot_number in range(0, len(self._slots[0].wics)): - # router_defaults["wic" + str(wic_slot_number)] = None + # add the wics + if self._slots[0] and self._slots[0].wics: + for wic_slot_number in range(0, len(self._slots[0].wics)): + if self._slots[0].wics[wic_slot_number]: + router_info["wic" + str(wic_slot_number)] = str(self._slots[0].wics[wic_slot_number]) return router_info @@ -312,9 +315,9 @@ class Router(BaseVM): if self._hypervisor and not self._hypervisor.devices: try: yield from self.stop() + yield from self._hypervisor.send('vm delete "{}"'.format(self._name)) except DynamipsError: pass - yield from self._hypervisor.send('vm delete "{}"'.format(self._name)) yield from self.hypervisor.stop() if self._console: @@ -1408,144 +1411,126 @@ class Router(BaseVM): self._private_config = private_config - # TODO: rename - # def rename(self, new_name): - # """ - # Renames this router. - # - # :param new_name: new name string - # """ - # - # if self._startup_config: - # # change the hostname in the startup-config - # startup_config_path = os.path.join(self.hypervisor.working_dir, "configs", "i{}_startup-config.cfg".format(self.id)) - # if os.path.isfile(startup_config_path): - # try: - # with open(startup_config_path, "r+", errors="replace") as f: - # old_config = f.read() - # new_config = old_config.replace(self.name, new_name) - # f.seek(0) - # f.write(new_config) - # except OSError as e: - # raise DynamipsError("Could not amend the configuration {}: {}".format(startup_config_path, e)) - # - # if self._private_config: - # # change the hostname in the private-config - # private_config_path = os.path.join(self.hypervisor.working_dir, "configs", "i{}_private-config.cfg".format(self.id)) - # if os.path.isfile(private_config_path): - # try: - # with open(private_config_path, "r+", errors="replace") as f: - # old_config = f.read() - # new_config = old_config.replace(self.name, new_name) - # f.seek(0) - # f.write(new_config) - # except OSError as e: - # raise DynamipsError("Could not amend the configuration {}: {}".format(private_config_path, e)) - # - # 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 + @asyncio.coroutine + def set_name(self, new_name): + """ + Renames this router. - # def set_config(self, startup_config, private_config=''): - # """ - # 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 - # :param private_config: path to private-config file - # (keep existing data when if an empty string) - # """ - # - # if self._startup_config != startup_config or self._private_config != private_config: - # - # self._hypervisor.send("vm set_config {name} {startup} {private}".format(name=self._name, - # 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 + '"')) - # - # self._startup_config = 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 + '"')) - # - # self._private_config = private_config - # - # def extract_config(self): - # """ - # Gets the contents of the config files - # startup-config and private-config from NVRAM. - # - # :returns: tuple (startup-config, private-config) base64 encoded - # """ - # - # try: - # reply = self._hypervisor.send("vm extract_config {}".format(self._name))[0].rsplit(' ', 2)[-2:] - # except IOError: - # #for some reason Dynamips gets frozen when it does not find the magic number in the NVRAM file. - # return None, None - # startup_config = reply[0][1:-1] # get statup-config and remove single quotes - # private_config = reply[1][1:-1] # get private-config and remove single quotes - # return startup_config, private_config - # - # def push_config(self, startup_config, private_config='(keep)'): - # """ - # Pushes configuration to the config files startup-config and private-config in NVRAM. - # The data is a Base64 encoded string, or '(keep)' to keep existing data. - # - # :param startup_config: statup-config string base64 encoded - # :param private_config: private-config string base64 encoded - # (keep existing data when if the value is ('keep')) - # """ - # - # self._hypervisor.send("vm push_config {name} {startup} {private}".format(name=self._name, - # 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)) - # - # def save_configs(self): - # """ - # Saves the startup-config and private-config to files. - # """ - # - # if self.startup_config or self.private_config: - # startup_config_base64, private_config_base64 = self.extract_config() - # if startup_config_base64: - # try: - # config = base64.decodebytes(startup_config_base64.encode("utf-8")).decode("utf-8") - # config = "!\n" + config.replace("\r", "") - # config_path = os.path.join(self.hypervisor.working_dir, self.startup_config) - # with open(config_path, "w") as f: - # log.info("saving startup-config to {}".format(self.startup_config)) - # f.write(config) - # except OSError as e: - # raise DynamipsError("Could not save the startup configuration {}: {}".format(config_path, e)) - # - # if private_config_base64: - # try: - # config = base64.decodebytes(private_config_base64.encode("utf-8")).decode("utf-8") - # config = "!\n" + config.replace("\r", "") - # config_path = os.path.join(self.hypervisor.working_dir, self.private_config) - # with open(config_path, "w") as f: - # log.info("saving private-config to {}".format(self.private_config)) - # f.write(config) - # except OSError as e: - # raise DynamipsError("Could not save the private configuration {}: {}".format(config_path, e)) + :param new_name: new name string + """ + + module_workdir = self.project.module_working_directory(self.manager.module_name.lower()) + if self._startup_config: + # change the hostname in the startup-config + startup_config_path = os.path.join(module_workdir, "configs", "i{}_startup-config.cfg".format(self._dynamips_id)) + if os.path.isfile(startup_config_path): + try: + with open(startup_config_path, "r+", errors="replace") as f: + old_config = f.read() + new_config = old_config.replace(self.name, new_name) + f.seek(0) + f.write(new_config) + except OSError as e: + raise DynamipsError("Could not amend the configuration {}: {}".format(startup_config_path, e)) + + if self._private_config: + # change the hostname in the private-config + private_config_path = os.path.join(module_workdir, "configs", "i{}_private-config.cfg".format(self._dynamips_id)) + if os.path.isfile(private_config_path): + try: + with open(private_config_path, "r+", errors="replace") as f: + old_config = f.read() + new_config = old_config.replace(self.name, new_name) + f.seek(0) + f.write(new_config) + except OSError as e: + raise DynamipsError("Could not amend the configuration {}: {}".format(private_config_path, e)) + + yield from self._hypervisor.send('vm rename "{name}" "{new_name}"'.format(name=self._name, new_name=new_name)) + log.info('Router "{name}" [{id}]: renamed to "{new_name}"'.format(name=self._name, id=self._id, new_name=new_name)) + self._name = new_name + + @asyncio.coroutine + def set_config(self, startup_config, private_config=''): + """ + 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 + :param private_config: path to private-config file + (keep existing data when if an empty string) + """ + + startup_config = startup_config.replace("\\", '/') + private_config = private_config.replace("\\", '/') + + if self._startup_config != startup_config or self._private_config != private_config: + + yield from self._hypervisor.send('vm set_config "{name}" "{startup}" "{private}"'.format(name=self._name, + startup=startup_config, + private=private_config)) + + log.info('Router "{name}" [{id}]: has a new startup-config set: "{startup}"'.format(name=self._name, + id=self._id, + startup=startup_config)) + + self._startup_config = startup_config + + if private_config: + log.info('Router "{name}" [{id}]: has a new private-config set: "{private}"'.format(name=self._name, + id=self._id, + private=private_config)) + + self._private_config = private_config + + @asyncio.coroutine + def extract_config(self): + """ + Gets the contents of the config files + startup-config and private-config from NVRAM. + + :returns: tuple (startup-config, private-config) base64 encoded + """ + + try: + reply = yield from self._hypervisor.send("vm extract_config {}".format(self._name))[0].rsplit(' ', 2)[-2:] + except IOError: + #for some reason Dynamips gets frozen when it does not find the magic number in the NVRAM file. + return None, None + startup_config = reply[0][1:-1] # get statup-config and remove single quotes + private_config = reply[1][1:-1] # get private-config and remove single quotes + return startup_config, private_config + + @asyncio.coroutine + def save_configs(self): + """ + Saves the startup-config and private-config to files. + """ + + if self.startup_config or self.private_config: + module_workdir = self.project.module_working_directory(self.manager.module_name.lower()) + startup_config_base64, private_config_base64 = yield from self.extract_config() + if startup_config_base64: + try: + config = base64.decodebytes(startup_config_base64.encode("utf-8")).decode("utf-8") + config = "!\n" + config.replace("\r", "") + config_path = os.path.join(module_workdir, self.startup_config) + with open(config_path, "w") as f: + log.info("saving startup-config to {}".format(self.startup_config)) + f.write(config) + except OSError as e: + raise DynamipsError("Could not save the startup configuration {}: {}".format(config_path, e)) + + if private_config_base64: + try: + config = base64.decodebytes(private_config_base64.encode("utf-8")).decode("utf-8") + config = "!\n" + config.replace("\r", "") + config_path = os.path.join(module_workdir, self.private_config) + with open(config_path, "w") as f: + log.info("saving private-config to {}".format(self.private_config)) + f.write(config) + except OSError as e: + raise DynamipsError("Could not save the private configuration {}: {}".format(config_path, e)) def delete(self): """ @@ -1555,6 +1540,20 @@ class Router(BaseVM): # delete the VM files project_dir = os.path.join(self.project.module_working_directory(self.manager.module_name.lower())) files = glob.glob(os.path.join(project_dir, "{}_i{}*".format(self._platform, self._dynamips_id))) + + module_workdir = self.project.module_working_directory(self.manager.module_name.lower()) + # delete the startup-config + if self._startup_config: + startup_config_path = os.path.join(module_workdir, "configs", "i{}_startup-config.cfg".format(self._dynamips_id)) + if os.path.isfile(startup_config_path): + files.append(startup_config_path) + + # delete the private-config + if self._private_config: + private_config_path = os.path.join(module_workdir, "configs", "i{}_private-config.cfg".format(self._dynamips_id)) + if os.path.isfile(private_config_path): + files.append(private_config_path) + for file in files: try: log.debug("Deleting file {}".format(file)) @@ -1571,17 +1570,4 @@ class Router(BaseVM): yield from self._hypervisor.send('vm clean_delete "{}"'.format(self._name)) self._hypervisor.devices.remove(self) - - # if self._startup_config: - # # delete the startup-config - # startup_config_path = os.path.join(self.hypervisor.working_dir, "configs", "{}.cfg".format(self.name)) - # if os.path.isfile(startup_config_path): - # os.remove(startup_config_path) - # - # if self._private_config: - # # delete the private-config - # private_config_path = os.path.join(self.hypervisor.working_dir, "configs", "{}-private.cfg".format(self.name)) - # if os.path.isfile(private_config_path): - # os.remove(private_config_path) - log.info('Router "{name}" [{id}] has been deleted (including associated files)'.format(name=self._name, id=self._id)) diff --git a/gns3server/schemas/dynamips_vm.py b/gns3server/schemas/dynamips_vm.py index c30d2920..0d767dd9 100644 --- a/gns3server/schemas/dynamips_vm.py +++ b/gns3server/schemas/dynamips_vm.py @@ -62,11 +62,19 @@ VM_CREATE_SCHEMA = { "type": "string", "minLength": 1, }, + "startup_config_content": { + "description": "Content of IOS startup configuration file", + "type": "string", + }, "private_config": { "description": "path to the IOS private configuration file", "type": "string", "minLength": 1, }, + "private_config_content": { + "description": "Content of IOS private configuration file", + "type": "string", + }, "ram": { "description": "amount of RAM in MB", "type": "integer"