From c3c78f99a19d6f982dfbd2e293f98180fc7e3dfe Mon Sep 17 00:00:00 2001 From: YueGuobin Date: Sun, 14 Jun 2026 12:15:30 +0800 Subject: [PATCH] revert: Remove _configs_map changes in tools_v2 (handled by template renderer now) --- .../tools_v2/config_tools_nornir.py | 14 ++++-------- .../tools_v2/display_tools_nornir.py | 14 ++++-------- .../tools_v2/vpcs_tools_netmiko.py | 15 ++++--------- gns3server/api/routes/mcp/device_config.py | 22 ++++++++++++------- 4 files changed, 26 insertions(+), 39 deletions(-) diff --git a/gns3server/agent/gns3_copilot/tools_v2/config_tools_nornir.py b/gns3server/agent/gns3_copilot/tools_v2/config_tools_nornir.py index 28d04555f..41ed58f9d 100644 --- a/gns3server/agent/gns3_copilot/tools_v2/config_tools_nornir.py +++ b/gns3server/agent/gns3_copilot/tools_v2/config_tools_nornir.py @@ -543,18 +543,12 @@ class ExecuteMultipleDeviceConfigCommands(BaseTool): def _configs_map( self, device_config_list: list[dict[str, Any]] ) -> dict[str, list[str]]: - """Create a mapping of device names to their configuration commands. - - Merges commands when the same device appears multiple times in the list. - """ - device_configs_map: dict[str, list[str]] = {} + """Create a mapping of device names to their configuration commands.""" + device_configs_map = {} for device_config in device_config_list: device_name = device_config["device_name"] - commands = device_config.get("config_commands", []) - if device_name in device_configs_map: - device_configs_map[device_name].extend(commands) - else: - device_configs_map[device_name] = list(commands) + config_commands = device_config["config_commands"] + device_configs_map[device_name] = config_commands return device_configs_map diff --git a/gns3server/agent/gns3_copilot/tools_v2/display_tools_nornir.py b/gns3server/agent/gns3_copilot/tools_v2/display_tools_nornir.py index f79ce6e75..8fa056650 100644 --- a/gns3server/agent/gns3_copilot/tools_v2/display_tools_nornir.py +++ b/gns3server/agent/gns3_copilot/tools_v2/display_tools_nornir.py @@ -486,18 +486,12 @@ class ExecuteMultipleDeviceCommands(BaseTool): def _configs_map( self, device_config_list: list[dict[str, Any]] ) -> dict[str, list[str]]: - """Create a mapping of device names to their diagnostic commands. - - Merges commands when the same device appears multiple times in the list. - """ - device_diagnostic_map: dict[str, list[str]] = {} + """Create a mapping of device names to their diagnostic commands.""" + device_diagnostic_map = {} for device_config in device_config_list: device_name = device_config["device_name"] - commands = device_config.get("commands", []) - if device_name in device_diagnostic_map: - device_diagnostic_map[device_name].extend(commands) - else: - device_diagnostic_map[device_name] = list(commands) + diagnostic_commands = device_config["commands"] + device_diagnostic_map[device_name] = diagnostic_commands return device_diagnostic_map diff --git a/gns3server/agent/gns3_copilot/tools_v2/vpcs_tools_netmiko.py b/gns3server/agent/gns3_copilot/tools_v2/vpcs_tools_netmiko.py index b561f9ba2..221bf9bed 100644 --- a/gns3server/agent/gns3_copilot/tools_v2/vpcs_tools_netmiko.py +++ b/gns3server/agent/gns3_copilot/tools_v2/vpcs_tools_netmiko.py @@ -405,23 +405,16 @@ class VPCSCommands(BaseTool): """ Create a mapping of device names to their command lists. - Merges commands when the same device appears multiple times. - Args: device_config_list: List of device configurations Returns: Dictionary mapping device names to command lists """ - cmd_map: dict[str, list[str]] = {} - for config in device_config_list: - name = config["device_name"] - cmds = config.get("commands", []) - if name in cmd_map: - cmd_map[name].extend(cmds) - else: - cmd_map[name] = list(cmds) - return cmd_map + return { + config["device_name"]: config["commands"] + for config in device_config_list + } def _prepare_device_hosts_data( self, diff --git a/gns3server/api/routes/mcp/device_config.py b/gns3server/api/routes/mcp/device_config.py index a3313b2da..f7a2009ae 100644 --- a/gns3server/api/routes/mcp/device_config.py +++ b/gns3server/api/routes/mcp/device_config.py @@ -41,6 +41,9 @@ log = logging.getLogger(__name__) def _render_template(template: str, device_configs: list[dict], commands_field: str = "config_commands") -> list[dict]: """Render a Jinja2 template for each device's vars into the specified commands field. + Entries with the same device_name are merged into a single entry + so they share one Nornir session and avoid output fragmentation. + Each device in device_configs can have: - "vars": dict of template variables (rendered into commands_field) - commands_field: existing commands merged after rendering if present @@ -49,22 +52,25 @@ def _render_template(template: str, device_configs: list[dict], commands_field: commands_field: field name for the rendered commands, e.g. "config_commands", "commands" """ jinja = JinjaTemplate(template) - results = [] + merged: dict[str, dict] = {} for dev in device_configs: - rendered = dev.copy() - vars_data = rendered.pop("vars", {}) + name = dev.get("device_name") + if not name: + continue + vars_data = dev.get("vars", {}) + if name not in merged: + merged[name] = {"device_name": name, commands_field: list(dev.get(commands_field, []))} + entry = merged[name] if vars_data: try: output = jinja.render(**vars_data) lines = [l for l in output.splitlines() if l.strip()] - existing = rendered.get(commands_field, []) - rendered[commands_field] = existing + lines + entry[commands_field].extend(lines) except JinjaError as e: - error_msg = f"Template rendering failed for '{dev.get('device_name')}': {e}" + error_msg = f"Template rendering failed for '{name}': {e}" log.error(error_msg) return [{"error": error_msg}] - results.append(rendered) - return results + return list(merged.values()) # ── Tool handlers ──────────────────────────────────────────────────────────