diff --git a/gns3server/handlers/iou_handler.py b/gns3server/handlers/iou_handler.py index 88e8aceb..04177f73 100644 --- a/gns3server/handlers/iou_handler.py +++ b/gns3server/handlers/iou_handler.py @@ -56,7 +56,8 @@ class IOUHandler: ethernet_adapters=request.json.get("ethernet_adapters"), ram=request.json.get("ram"), nvram=request.json.get("nvram"), - l1_keepalives=request.json.get("l1_keepalives") + l1_keepalives=request.json.get("l1_keepalives"), + initial_config=request.json.get("initial_config") ) vm.path = request.json.get("path", vm.path) vm.iourc_path = request.json.get("iourc_path", vm.iourc_path) @@ -112,6 +113,7 @@ class IOUHandler: vm.ram = request.json.get("ram", vm.ram) vm.nvram = request.json.get("nvram", vm.nvram) vm.l1_keepalives = request.json.get("l1_keepalives", vm.l1_keepalives) + vm.initial_config = request.json.get("initial_config", vm.initial_config) response.json(vm) diff --git a/gns3server/modules/iou/iou_vm.py b/gns3server/modules/iou/iou_vm.py index 6c6bc02f..5f953638 100644 --- a/gns3server/modules/iou/iou_vm.py +++ b/gns3server/modules/iou/iou_vm.py @@ -62,7 +62,8 @@ class IOUVM(BaseVM): :params serial_adapters: Number of serial adapters :params ram: Ram MB :params nvram: Nvram KB - :params l1_keepalives: Always up ethernet interface + :params l1_keepalives: Always up ethernet interface: + :params initial_config: Content of the initial configuration file """ def __init__(self, name, vm_id, project, manager, @@ -72,7 +73,8 @@ class IOUVM(BaseVM): nvram=None, ethernet_adapters=None, serial_adapters=None, - l1_keepalives=None): + l1_keepalives=None, + initial_config=None): super().__init__(name, vm_id, project, manager) @@ -98,6 +100,9 @@ class IOUVM(BaseVM): self._ram = 256 if ram is None else ram # Megabytes self._l1_keepalives = False if l1_keepalives is None else l1_keepalives # used to overcome the always-up Ethernet interfaces (not supported by all IOSes). + if initial_config is not None: + self.initial_config = initial_config + if self._console is not None: self._console = self._manager.port_manager.reserve_console_port(self._console) else: @@ -212,7 +217,7 @@ class IOUVM(BaseVM): "serial_adapters": len(self._serial_adapters), "ram": self._ram, "nvram": self._nvram, - "l1_keepalives": self._l1_keepalives + "l1_keepalives": self._l1_keepalives, } @property @@ -303,6 +308,21 @@ class IOUVM(BaseVM): new_nvram=nvram)) self._nvram = nvram + @BaseVM.name.setter + def name(self, new_name): + """ + Sets the name of this IOU vm. + + :param new_name: name + """ + + if self.initial_config_file: + content = self.initial_config + content = content.replace(self._name, new_name) + self.initial_config = content + + super(IOUVM, IOUVM).name.__set__(self, new_name) + @property def application_id(self): return self._manager.get_application_id(self.id) @@ -614,8 +634,10 @@ class IOUVM(BaseVM): command.extend(["-n", str(self._nvram)]) command.extend(["-m", str(self._ram)]) command.extend(["-L"]) # disable local console, use remote console - if self._initial_config: - command.extend(["-c", self._initial_config]) + + initial_config_file = self.initial_config_file + if initial_config_file: + command.extend(["-c", initial_config_file]) if self._l1_keepalives: self._enable_l1_keepalives(command) command.extend([str(self.application_id)]) @@ -813,3 +835,50 @@ class IOUVM(BaseVM): raise IOUError("layer 1 keepalive messages are not supported by {}".format(os.path.basename(self._path))) except (OSError, subprocess.SubprocessError) as e: log.warn("could not determine if layer 1 keepalive messages are supported by {}: {}".format(os.path.basename(self._path), e)) + + @property + def initial_config(self): + """Return the content of the current initial-config file""" + + config_file = self.initial_config_file + if config_file is None: + return None + + try: + with open(config_file) as f: + return f.read() + except OSError as e: + raise VPCSError("Can't read configuration file '{}'".format(config_file)) + + @initial_config.setter + def initial_config(self, initial_config): + """ + Update the initial config + + :param initial_config: The content of the initial configuration file + """ + + try: + script_file = os.path.join(self.working_dir, "initial-config.cfg") + with open(script_file, 'w+') as f: + if initial_config is None: + f.write('') + else: + initial_config = initial_config.replace("%h", self._name) + f.write(initial_config) + except OSError as e: + raise VPCSError("Can't write initial configuration file '{}'".format(self.script_file)) + + @property + def initial_config_file(self): + """ + Returns the initial config file for this IOU instance. + + :returns: path to config file. None if the file doesn't exist + """ + + path = os.path.join(self.working_dir, 'initial-config.cfg') + if os.path.exists(path): + return path + else: + return None diff --git a/gns3server/schemas/iou.py b/gns3server/schemas/iou.py index 0f578cf0..857208c3 100644 --- a/gns3server/schemas/iou.py +++ b/gns3server/schemas/iou.py @@ -69,6 +69,10 @@ IOU_CREATE_SCHEMA = { "l1_keepalives": { "description": "Always up ethernet interface", "type": ["boolean", "null"] + }, + "initial_config": { + "description": "Initial configuration of the IOU", + "type": ["string", "null"] } }, "additionalProperties": False, @@ -122,6 +126,10 @@ IOU_UPDATE_SCHEMA = { "l1_keepalives": { "description": "Always up ethernet interface", "type": ["boolean", "null"] + }, + "initial_config": { + "description": "Initial configuration of the IOU", + "type": ["string", "null"] } }, "additionalProperties": False, @@ -180,7 +188,7 @@ IOU_OBJECT_SCHEMA = { "l1_keepalives": { "description": "Always up ethernet interface", "type": "boolean" - } + }, }, "additionalProperties": False, "required": ["name", "vm_id", "console", "project_id", "path", "serial_adapters", "ethernet_adapters", "ram", "nvram", "l1_keepalives"] diff --git a/tests/api/test_iou.py b/tests/api/test_iou.py index f22090da..eaffed13 100644 --- a/tests/api/test_iou.py +++ b/tests/api/test_iou.py @@ -46,6 +46,9 @@ def vm(server, project, base_params): return response.json +def initial_config_file(project, vm): + return os.path.join(project.path, "project-files", "iou", vm["vm_id"], "initial-config.cfg") + def test_iou_create(server, project, base_params): response = server.post("/projects/{project_id}/iou/vms".format(project_id=project.id), base_params) assert response.status == 201 @@ -66,6 +69,7 @@ def test_iou_create_with_params(server, project, base_params): params["serial_adapters"] = 4 params["ethernet_adapters"] = 0 params["l1_keepalives"] = True + params["initial_config"] = "hostname test" response = server.post("/projects/{project_id}/iou/vms".format(project_id=project.id), params, example=True) assert response.status == 201 @@ -77,6 +81,8 @@ def test_iou_create_with_params(server, project, base_params): assert response.json["ram"] == 1024 assert response.json["nvram"] == 512 assert response.json["l1_keepalives"] == True + with open(initial_config_file(project, response.json)) as f: + assert f.read() == params["initial_config"] def test_iou_get(server, project, vm): @@ -120,7 +126,7 @@ def test_iou_delete(server, vm): assert response.status == 204 -def test_iou_update(server, vm, tmpdir, free_console_port): +def test_iou_update(server, vm, tmpdir, free_console_port, project): params = { "name": "test", "console": free_console_port, @@ -128,7 +134,8 @@ def test_iou_update(server, vm, tmpdir, free_console_port): "nvram": 2048, "ethernet_adapters": 4, "serial_adapters": 0, - "l1_keepalives": True + "l1_keepalives": True, + "initial_config": "hostname test" } response = server.put("/projects/{project_id}/iou/vms/{vm_id}".format(project_id=vm["project_id"], vm_id=vm["vm_id"]), params) assert response.status == 200 @@ -139,7 +146,8 @@ def test_iou_update(server, vm, tmpdir, free_console_port): assert response.json["ram"] == 512 assert response.json["nvram"] == 2048 assert response.json["l1_keepalives"] == True - + with open(initial_config_file(project, response.json)) as f: + assert f.read() == "hostname test" def test_iou_nio_create_udp(server, vm): response = server.post("/projects/{project_id}/iou/vms/{vm_id}/adapters/1/ports/0/nio".format(project_id=vm["project_id"], vm_id=vm["vm_id"]), {"type": "nio_udp", diff --git a/tests/modules/iou/test_iou_vm.py b/tests/modules/iou/test_iou_vm.py index 55ad02a3..55d80a00 100644 --- a/tests/modules/iou/test_iou_vm.py +++ b/tests/modules/iou/test_iou_vm.py @@ -69,6 +69,12 @@ def test_vm(project, manager): assert vm.id == "00010203-0405-0607-0809-0a0b0c0d0e0f" +def test_vm_initial_config(project, manager): + vm = IOUVM("test", "00010203-0405-0607-0808-0a0b0c0d0e0f", project, manager, initial_config="hostname %h") + assert vm.name == "test" + assert vm.initial_config == "hostname test" + assert vm.id == "00010203-0405-0607-0808-0a0b0c0d0e0f" + @patch("gns3server.config.Config.get_section_config", return_value={"iouyap_path": "/bin/test_fake"}) def test_vm_invalid_iouyap_path(project, manager, loop): with pytest.raises(IOUError): @@ -179,4 +185,48 @@ def test_create_netmap_config(vm): def test_build_command(vm): - assert vm._build_command() == [vm.path, '-L', str(vm.application_id)] + assert vm._build_command() == [vm.path, "-L", str(vm.application_id)] + + +def test_build_command_initial_config(vm): + + filepath = os.path.join(vm.working_dir, "initial-config.cfg") + with open(filepath, "w+") as f: + f.write("service timestamps debug datetime msec\nservice timestamps log datetime msec\nno service password-encryption") + + assert vm._build_command() == [vm.path, "-L", "-c", vm.initial_config_file, str(vm.application_id)] + + +def test_get_initial_config(vm): + + content = "service timestamps debug datetime msec\nservice timestamps log datetime msec\nno service password-encryption" + vm.initial_config = content + assert vm.initial_config == content + + +def test_update_initial_config(vm): + content = "service timestamps debug datetime msec\nservice timestamps log datetime msec\nno service password-encryption" + vm.initial_config = content + filepath = os.path.join(vm.working_dir, "initial-config.cfg") + assert os.path.exists(filepath) + with open(filepath) as f: + assert f.read() == content + + +def test_update_initial_config_h(vm): + content = "hostname %h\n" + vm.name = "pc1" + vm.initial_config = content + with open(vm.initial_config_file) as f: + assert f.read() == "hostname pc1\n" + + +def test_change_name(vm, tmpdir): + path = os.path.join(vm.working_dir, "initial-config.cfg") + vm.name = "world" + with open(path, 'w+') as f: + f.write("hostname world") + vm.name = "hello" + assert vm.name == "hello" + with open(path) as f: + assert f.read() == "hostname hello"