gns3-server/docs/gns3-copilot/implemented/multi-vendor-device-support.md
YueGuobin 6e88582ff1 docs(gns3-copilot): update documentation for Ruijie driver and new features
- Add gns3_ruijie_telnet to supported devices in netmiko_devices.md
- Document custom RuijieTelnetEnhanced driver with hybrid strategy
  - Preprocessing: auto-insert 'yes' after known interactive commands
  - Fast path: batch send (2-3s for 13 commands)
  - Fallback: one-by-one with real-time prompt detection
- Add AAA/password configuration prohibition documentation
  - System-level enforcement in lab_automation_assistant_prompt
  - Categories: AAA, login passwords, console/VTY auth, encryption
- Document multi-line command handling for banner and similar commands
- Update device_type tag validation with error feedback
- Update multi-vendor support to include Ruijie platform

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-03-13 23:56:57 +08:00

28 KiB

Multi-Vendor Network Device Support

Overview

GNS3-Copilot supports network devices from multiple vendors through Netmiko and Nornir integration. The system includes a custom Netmiko driver for Huawei devices in GNS3 emulation environments and supports dynamic device type detection.

Supported Vendors

Vendor Platform Device Type Protocol Status
Cisco cisco_ios cisco_ios_telnet Telnet Tested
Huawei huawei huawei_telnet_ce Telnet Tested (Custom Driver)
Ruijie (锐捷) ruijie_os gns3_ruijie_telnet Telnet Tested (Custom Driver)

Custom Huawei Driver (HuaweiTelnetCE)

Problem Statement

GNS3-emulated Huawei devices connect via console without requiring authentication. Standard Netmiko drivers attempt username/password authentication, causing connection timeouts.

Standard Driver Behavior:

Telnet Connection → Wait for username prompt → Send username → Wait for password → Send password → Access
                    ^ Times out after 20 seconds

GNS3 Huawei Device:

Telnet Connection → Direct access to command line (no login prompts)
                    <HUAWEI>

Solution: Custom Driver Architecture

BaseConnection (Netmiko base class)
    ↓
CiscoBaseConnection (Cisco-style base class)
    ↓
HuaweiBase (Huawei device base class) ← Inherits VRP support
    ↓
HuaweiTelnetCE (Custom GNS3 driver) ← Overrides telnet_login only

Why Inherit from HuaweiBase?

  • Built-in VRP (Versatile Routing Platform) command handling
  • Huawei-specific configuration mode (system-view)
  • Huawei prompt patterns (<...>, [...])
  • Huawei paging disable (screen-length 0 temporary)
  • Minimal code changes - only override authentication

HuaweiTelnetCE Implementation

Location

gns3server/agent/gns3_copilot/utils/custom_netmiko/huawei_ce.py

Package Structure:

custom_netmiko/
├── __init__.py              # Package initialization, auto-registers all drivers
├── huawei_ce.py             # Huawei CloudEngine custom driver
├── README.md                # Driver development guide
└── tests/                   # Unit tests
    ├── __init__.py
    └── test_huawei_ce.py    # Huawei CE driver tests

Key Features

  1. Skip Authentication

    • Directly detect Huawei prompt patterns
    • No username/password prompts
    • Connection ready in < 1 second
  2. VRP Prompt Recognition

    User view:    <HUAWEI>
    System view:  [HUAWEI]
    Interface:    [HUAWEI-GigabitEthernet0/0/1]
    
  3. Automatic Confirmation Handling

    • Detects and responds to [y/n] prompts
    • Example: return command asks "Return to user view? [y/n]:"
    • Automatically sends y to confirm
  4. Proper Output Collection

    • Uses Netmiko's read_channel_timing() for reliable output
    • Waits for command completion (2s no new data = done)
    • 30-second absolute timeout prevents hanging
  5. Auto-Commit Before Exit

    • Automatically sends commit command before exiting config mode
    • Prevents "Uncommitted configurations [Y/N/C]" prompt
    • Ensures configuration changes are saved

Limitations

Authentication Requirement:

  • The huawei_telnet_ce driver is designed for GNS3 devices without authentication
  • If your Huawei device has been configured with a username/password:
    • Option 1: Use the standard huawei_telnet driver (requires username/password)
    • Option 2: Remove authentication from the device for GNS3 testing
  • The driver does not currently auto-detect authentication requirements

When to Use Each Driver:

Scenario Use Driver Requires Credentials?
GNS3 Huawei (fresh, no auth) huawei_telnet_ce No
GNS3 Huawei (configured with username/password) huawei_telnet Yes
Real Huawei hardware huawei_telnet Yes

Method Overrides

1. telnet_login - Skip Authentication

def telnet_login(self, pri_prompt_terminator=r"<\S+>|>\s*$",
                alt_prompt_terminator=r"\[\S+\]", ...) -> str:
    # Clear buffer
    self.read_channel()

    # Send returns until prompt detected
    for i in range(max_loops):
        self.write_channel(self.RETURN)
        output = self.read_channel()

        # Check for Huawei prompts
        if re.search(pri_prompt_terminator, output):
            return output  # Success!

    return output  # Best effort

2. send_config_set - Configuration Commands

def send_config_set(self, config_commands, **kwargs) -> str:
    # Enter config mode
    output += self.config_mode(config_command="system-view")

    # Send all commands
    for cmd in config_commands:
        self.write_channel(f"{cmd}{self.RETURN}")
        time.sleep(delay_factor * 0.05)

    # Collect output using Netmiko standard method
    output += self.read_channel_timing(read_timeout=30, last_read=2.0)

    # Auto-commit before exit (prevents [Y/N/C] prompt)
    self.write_channel(f"commit{self.RETURN}")
    time.sleep(0.5 * self.global_delay_factor)
    output += self.read_channel()

    # Exit config mode
    output += self.exit_config_mode()

    return output

3. exit_config_mode - Handle Confirmation

def exit_config_mode(self, exit_config="return", pattern=r"<\S+>|>\s*$") -> str:
    self.write_channel(f"return{self.RETURN}")

    # Look for confirmation prompt
    for _ in range(20):
        new_output = self.read_channel()

        if re.search(r"\[y/n\]", new_output):
            self.write_channel(f"y{self.RETURN}")  # Auto-confirm

        if re.search(pattern, new_output):
            return output  # Back to user view

    return output

Device Type Registration

The custom driver must be registered with Netmiko's global mappings:

def register_custom_device_type() -> None:
    import importlib
    sd = importlib.import_module("netmiko.ssh_dispatcher")

    # Register in CLASS_MAPPER (for ConnectHandler)
    sd.CLASS_MAPPER["huawei_telnet_ce"] = HuaweiTelnetCE
    sd.CLASS_MAPPER["huawei_ce"] = HuaweiTelnetCE

    # Register in CLASS_MAPPER_BASE (for base class definitions)
    sd.CLASS_MAPPER_BASE["huawei_telnet_ce"] = HuaweiTelnetCE
    sd.CLASS_MAPPER_BASE["huawei_ce"] = HuaweiTelnetCE

    # CRITICAL: Rebuild static lists
    sd.platforms = list(sd.CLASS_MAPPER.keys())
    sd.platforms.sort()
    sd.telnet_platforms = [x for x in sd.platforms if "telnet" in x]

Important: Static List Problem

  • ssh_dispatcher.platforms is computed at module import time
  • Modifying CLASS_MAPPER doesn't automatically update platforms
  • Must manually rebuild the list after registration

Auto-Registration

# Automatically runs on module import
try:
    register_custom_device_type()
except Exception as e:
    logger.warning(f"Failed to register custom device type: {e}")

Custom Ruijie Driver (RuijieTelnetEnhanced)

Problem Statement

Ruijie (锐捷) network devices exhibit Cisco-like command syntax but have interactive prompts during configuration that can cause standard Netmiko drivers to fail.

Common Issue - OSPF Router-ID:

Router(config-router)#router-id 10.0.0.1
Change router-id and update OSPF process! [yes/no]:

Standard Netmiko's send_config_set() waits for a prompt pattern, but the [yes/no]: prompt doesn't match the expected config mode pattern, causing:

  1. ReadTimeout: Netmiko times out waiting for the prompt
  2. Command Failure: Subsequent commands are not executed
  3. Device Lockup: Console remains in the waiting state

Solution: Hybrid Strategy with Interactive Prompt Handling

BaseConnection (Netmiko base class)
    ↓
CiscoBaseConnection (Cisco-style base class)
    ↓
RuijieOSBase (Netmiko's Ruijie implementation)
    ↓
RuijieTelnetEnhanced (Custom GNS3 driver) ← Adds interactive prompt handling

Why Hybrid Strategy?

  1. Preprocessing: Automatically insert yes after known interactive commands
  2. Fast Path: Batch send for most commands (2-3 seconds for 13 commands)
  3. Fallback: One-by-one send with real-time detection (reliable but slower)

RuijieTelnetEnhanced Implementation

Location

gns3server/agent/gns3_copilot/utils/custom_netmiko/ruijie_telnet.py

Key Features

1. Preprocessing - Known Interactive Commands

INTERACTIVE_PATTERNS = [
    re.compile(r'^router-id\s+', re.IGNORECASE),  # OSPF/EIGRP/BGP router-id
    re.compile(r'^erase\s+', re.IGNORECASE),        # erase startup-config
    re.compile(r'^delete\s+', re.IGNORECASE),       # delete files
    re.compile(r'^format\s+', re.IGNORECASE),       # format filesystem
    re.compile(r'^reload\b', re.IGNORECASE),        # reload/reboot
    re.compile(r'^boot\s+system\s+', re.IGNORECASE), # change boot image
]

2. Hybrid Send Strategy

┌─────────────────────────────────────┐
│ Input Configuration Commands        │
└──────────────┬──────────────────────┘
               ↓
┌─────────────────────────────────────┐
│ Step 1: Preprocessing               │
│ - Detect interactive commands       │
│ - Insert 'yes' after them           │
└──────────────┬──────────────────────┘
               ↓
┌─────────────────────────────────────┐
│ Step 2: Try Batch Send (Fast)       │
│ - Write all commands rapidly        │
│ - Read output once                  │
│ - last_read=2.0s (Netmiko standard) │
└──────────────┬──────────────────────┘
               ↓
         Success? ──Yes──→ Return output
               │
               No
               ↓
┌─────────────────────────────────────┐
│ Step 3: Fallback (Slow but Reliable)│
│ - Send commands one-by-one          │
│ - Detect prompts after each command │
│ - Send 'yes' when needed            │
│ - last_read=0.5s per command        │
└─────────────────────────────────────┘

3. Batch Send Performance

  • 13 commands with 1 interactive command
  • Fast path: ~2-3 seconds (includes device processing time)
  • Fallback: ~10 seconds (if batch fails)

4. Real-time Detection (Fallback)

# After each command
new_output = self.read_channel_timing(read_timeout=10, last_read=0.5)

# Check for [yes/no] prompt
if re.search(r"\[yes/no\]", new_output, re.IGNORECASE):
    self.write_channel(f"yes{self.RETURN}")
    output += self.read_channel_timing(read_timeout=30, last_read=0.5)

GNS3 Node Tag Configuration

For Ruijie devices in GNS3:

device_type:gns3_ruijie_telnet
platform:ruijie_os

Example Usage:

from netmiko import ConnectHandler
from gns3server.agent.gns3_copilot.utils import custom_netmiko

device = {
    "device_type": "gns3_ruijie_telnet",
    "host": "127.0.0.1",
    "port": 5000,
}

with ConnectHandler(**device) as conn:
    # These commands include router-id (interactive)
    config = [
        "router ospf 1",
        "router-id 10.0.0.1",  # Triggers [yes/no] prompt
        "network 192.168.1.0 0.0.0.255 area 0",
    ]
    # Automatically handles the [yes/no] prompt
    output = conn.send_config_set(config)

Limitations

Interactive Command Coverage:

  • Covered: router-id, erase, delete, format, reload, boot system
  • Not Covered: Unknown or vendor-specific interactive prompts
  • Fallback: If batch fails, falls back to one-by-one with real-time detection

When to Use Each Driver:

Scenario Use Driver
GNS3 Ruijie (known commands) gns3_ruijie_telnet (batch works)
GNS3 Ruijie (unknown commands) gns3_ruijie_telnet (auto-fallback)
Real Ruijie hardware ruijie_os_telnet (standard)

Dynamic Device Type Detection

GNS3 Node Tags

Device type and platform are extracted from GNS3 node tags:

device_type:huawei_telnet_ce    → Netmiko device type (precise)
platform:huawei                  → Nornir platform (high-level)

Tag Examples:

Vendor Device Type Tag Platform Tag
Cisco IOS device_type:cisco_ios_telnet platform:cisco_ios
Huawei CE device_type:huawei_telnet_ce platform:huawei
Ruijie device_type:gns3_ruijie_telnet platform:ruijie_os

Nornir Best Practice: Host-Level Connection Configuration

The system uses Nornir's configuration priority (host > group > defaults) to handle multi-vendor environments efficiently:

# From get_gns3_device_port.py
hosts_data[device_name] = {
    "port": console_port,
    "platform": platform,  # Reserved for future use (NAPALM, scrapli)
    "groups": ["network_devices"],  # All devices share one group
    "connection_options": {
        "netmiko": {
            "extras": {"device_type": device_type}  # Device-specific driver
        }
    },
}

Why Host-Level connection_options?

  • Each device has its own device_type (host-level config)
  • All devices share common settings via group inheritance (hostname, timeout)
  • No need to dynamically create multiple groups for each device type
  • Cleaner code structure - single generic group for all devices
  • Follows Nornir best practice: "configuration proximity"

Configuration Priority:

Host Level (connection_options.device_type)
    ↓ OVERRIDES
Group Level (hostname, timeout, username, password)
    ↓ OVERRIDES
Defaults Level (data.location)

Before (Old Approach - Dynamic Groups):

# Had to create multiple groups dynamically
groups = {
    "cisco_ios_telnet": {"device_type": "cisco_ios_telnet", ...},
    "huawei_telnet": {"device_type": "huawei_telnet_ce", ...},
    "juniper_junos": {"device_type": "juniper_junos_telnet", ...},
}
# Each host assigned to its vendor-specific group

After (Current Approach - Host-Level Config):

# Single group for shared settings
groups = {
    "network_devices": {
        "hostname": "127.0.0.1",
        "timeout": 120,
        "username": "",
        "password": "",
    }
}
# Each host has device-specific connection_options
# Host config overrides group config automatically

Architecture Evolution

Problem: Multi-Vendor Device Support

Initial Challenge:

Topology: Cisco R1 + Huawei SW1 + Juniper SRX
    ↓
Need: Different Netmiko drivers for each device
    ↓
Question: How to configure Nornir for multiple device types?

Solution Evolution

Approach 1: Single Group with First Device's Type (Initial Implementation)

# PROBLEM: Only uses first device's configuration
def _initialize_nornir(hosts_data):
    first_device = next(iter(hosts_data.values()))
    device_type = first_device["device_type"]  # Only one type!

    return InitNornir(
        inventory={
            "options": {
                "hosts": hosts_data,  # Has multiple device types
                "groups": {
                    "network_devices": {
                        "connection_options": {
                            "netmiko": {"extras": {"device_type": device_type}}
                        }
                    }
                }
            }
        }
    )

Issue: All devices use the first device's driver!

  • Cisco R1 → Uses Huawei driver (if Huawei is first)
  • Huawei SW1 → Uses Cisco driver (if Cisco is first)

Approach 2: Dynamic Groups (Intermediate Solution)

# COMPLEX: Create multiple groups dynamically
groups = {}
for host_data in hosts_data.values():
    device_type = host_data["device_type"]
    platform = host_data["platform"]
    group_name = f"{platform}_telnet"  # e.g., "huawei_telnet"

    if group_name not in groups:
        groups[group_name] = {
            "platform": platform,
            "connection_options": {
                "netmiko": {"extras": {"device_type": device_type}}
            }
        }

    host_data["groups"] = [group_name]

Issues:

  • Complex logic to detect and create groups
  • Code duplication in multiple files
  • Had to delete helper functions (_get_nornir_groups_config, _get_nornir_group)
  • Not following Nornir best practices

Approach 3: Host-Level Configuration (Current - Best Practice)

# SIMPLE: Single group + host-level device_type
hosts_data[device_name] = {
    "port": console_port,
    "platform": platform,  # Reserved for future use
    "groups": ["network_devices"],  # All devices in one group
    "connection_options": {  # Device-specific config
        "netmiko": {
            "extras": {"device_type": device_type}
        }
    }
}

# Single generic group for shared settings
groups = {
    "network_devices": {
        "hostname": "127.0.0.1",
        "timeout": 120,
        "username": "",
        "password": "",
    }
}

Advantages:

  • Clean, simple code
  • Follows Nornir best practice (host > group > defaults)
  • No dynamic group creation logic
  • Each device's connection_options overrides group settings automatically
  • Easy to extend with new device types

Configuration Priority Demonstration

# Host level (highest priority)
host["connection_options"]["netmiko"]["extras"]["device_type"] = "huawei_telnet_ce"

     OVERRIDES

# Group level (middle priority)
group["hostname"] = "127.0.0.1"
group["timeout"] = 120

     OVERRIDES

# Defaults level (lowest priority)
defaults["data"]["location"] = "gns3"

Result:

  • Each device uses its own device_type from host level
  • All devices share hostname, timeout from group level
  • All devices share data.location from defaults level

Usage Examples

Direct Netmiko Usage

Huawei Device (Custom Driver):

from netmiko import ConnectHandler
from gns3server.agent.gns3_copilot.utils import custom_netmiko

# Custom driver auto-registers on import
device = {
    "device_type": "huawei_telnet_ce",
    "host": "127.0.0.1",
    "port": 5000,
    # No username/password needed!
}

with ConnectHandler(**device) as conn:
    # Execute display command
    output = conn.send_command("display version")

    # Execute configuration commands
    config = [
        "interface GE1/0/1",
        "description Uplink-to-Core",
        "undo shutdown"
    ]
    output = conn.send_config_set(config)

Cisco IOS Device (Standard Driver):

from netmiko import ConnectHandler

device = {
    "device_type": "cisco_ios_telnet",
    "host": "127.0.0.1",
    "port": 5001,
    "username": "cisco",
    "password": "cisco",
}

with ConnectHandler(**device) as conn:
    output = conn.send_command("show version")
    config = ["interface GigabitEthernet0/0", "description Test"]
    output = conn.send_config_set(config)

Nornir Multi-Vendor Automation

from nornir import InitNornir
from gns3server.agent.gns3_copilot.utils import custom_netmiko

# Auto-register custom driver (happens automatically on import)
from gns3server.agent.gns3_copilot.utils.custom_netmiko import huawei_ce
huawei_ce.register_custom_device_type()

# Initialize Nornir with mixed-vendor inventory
# Using host-level connection_options (best practice)
inventory = {
    "plugin": "DictInventory",
    "options": {
        "hosts": {
            "huawei-sw1": {
                "hostname": "127.0.0.1",
                "port": 5001,
                "platform": "huawei",  # Reserved for future use
                "groups": ["network_devices"],
                "connection_options": {
                    "netmiko": {
                        "extras": {"device_type": "huawei_telnet_ce"}
                    }
                }
            },
            "cisco-r1": {
                "hostname": "127.0.0.1",
                "port": 5002,
                "platform": "cisco_ios",
                "groups": ["network_devices"],
                "connection_options": {
                    "netmiko": {
                        "extras": {"device_type": "cisco_ios_telnet"}
                    }
                }
            }
        },
        "groups": {
            "network_devices": {
                "hostname": "127.0.0.1",  # Shared by all devices
                "timeout": 120,
                "username": "",
                "password": "",
            }
        },
        "defaults": {
            "data": {"location": "gns3"}
        }
    }
}

nr = InitNornir(inventory=inventory)

# Execute commands on all devices (multi-vendor)
result = nr.run(task=send_commands, commands=["display version"])

# Each device gets vendor-specific command handling
# huawei-sw1 uses huawei_telnet_ce driver
# cisco-r1 uses cisco_ios_telnet driver

GNS3 Copilot Tool Usage

from gns3server.agent.gns3_copilot.tools_v2 import DisplayToolNornir

tool = DisplayToolNornir()
result = tool._run(json.dumps({
    "device_names": ["huawei-sw1", "cisco-r1"],
    "commands": ["display version", "show version"],
    "project_id": "project-uuid"
}))

# Returns:
# {
#   "huawei-sw1": {
#     "display version": "<Huawei output>",
#     "status": "success"
#   },
#   "cisco-r1": {
#     "show version": "<Cisco output>",
#     "status": "success"
#   }
# }

Module Structure

gns3server/agent/gns3_copilot/
├── utils/
│   ├── custom_netmiko/            # Custom Netmiko drivers package
│   │   ├── __init__.py             # Package initialization
│   │   ├── huawei_ce.py            # Huawei CloudEngine driver
│   │   ├── ruijie_telnet.py        # Ruijie enhanced driver (NEW)
│   │   ├── README.md               # Driver development guide
│   │   └── tests/                  # Unit tests
│   │       ├── __init__.py
│   │       └── test_huawei_ce.py   # Huawei CE driver tests
│   └── get_gns3_device_port.py     # Device port extraction with host-level config
│       ├── _expand_multiline_commands()   # Expand banner commands (NEW)
│       └── _error_handling()              # device_type missing errors (NEW)
├── tools_v2/
│   ├── display_tools_nornir.py     # Multi-vendor display commands
│   │   ├── _get_nornir_defaults()  # Returns default Nornir config
│   │   └── _initialize_nornir()    # Single generic group + host-level device_type
│   └── config_tools_nornir.py      # Multi-vendor config commands
│       ├── _get_nornir_defaults()  # Returns default Nornir config
│       └── _initialize_nornir()    # Single generic group + host-level device_type
│       ├── _expand_multiline_commands()   # Expand banner commands (NEW)
│       └── _error_handling()              # device_type validation (NEW)

Key Architectural Changes (2026-03-13):

  • Removed: _get_nornir_groups_config() - No longer needed
  • Removed: _get_nornir_group() - No longer needed
  • Simplified: _initialize_nornir() - Uses single generic group
  • Updated: get_gns3_device_port.py() - Returns host-level connection_options
  • Added: ruijie_telnet.py - Custom Ruijie driver with interactive prompt handling
  • Added: _expand_multiline_commands() - Auto-expands banner and multi-line commands
  • Added: _error_handling() - Validates device_type tags, returns error if missing

Unit Testing

Test Coverage

# test_netmiko_custom.py

class TestHuaweiTelnetCEDriver(unittest.TestCase):
    def test_device_type_registered(self):
        """Verify huawei_telnet_ce is in Netmiko CLASS_MAPPER"""
        from netmiko.ssh_dispatcher import CLASS_MAPPER
        self.assertIn("huawei_telnet_ce", CLASS_MAPPER)

    def test_inheritance_from_huawei_base(self):
        """Verify inherits from HuaweiBase"""
        from netmiko.huawei.huawei import HuaweiBase
        self.assertTrue(issubclass(HuaweiTelnetCE, HuaweiBase))

    def test_vrp_methods_available(self):
        """Verify VRP-specific methods are available"""
        methods = ["config_mode", "check_config_mode", "exit_config_mode"]
        for method in methods:
            self.assertTrue(hasattr(HuaweiTelnetCE, method))

Running Tests:

source venv/bin/activate
python gns3server/agent/gns3_copilot/utils/custom_netmiko/tests/test_huawei_ce.py

Current Test Status: All 9 tests passing

Platform vs Device Type

Key Concepts

Platform (Nornir):

  • High-level vendor identifier
  • Reserved for future use with plugins like NAPALM, scrapli
  • Used for metadata and logging
  • Examples: huawei, cisco_ios
  • ⚠️ Not used by nornir_netmiko (only device_type matters)

Device Type (Netmiko):

  • Precise driver type for Netmiko connection
  • Includes protocol information
  • Actively used to determine which Netmiko driver class to load
  • Examples: huawei_telnet_ce, cisco_ios_telnet

Why Keep platform Field?

Purpose Plugin Uses platform?
Connection driver nornir_netmiko No (uses device_type)
Driver selection NAPALM Yes
Driver selection scrapli Yes
Metadata/Logging General Yes (future)

Conclusion: The platform field is kept for:

  1. Future plugin support (NAPALM, scrapli)
  2. Debugging and logging (vendor identification)
  3. Data completeness (industry standard practice)

Mapping

Platform Device Type Netmiko Usage Notes
huawei huawei_telnet_ce Active Custom driver for GNS3
cisco_ios cisco_ios_telnet Active Standard Netmiko driver

Important: For nornir_netmiko, only device_type in connection_options matters. The platform field is informational only.

References


Implementation Date: 2026-03-12

Last Updated: 2026-03-13 (Added Ruijie driver, multi-line command expansion, and configuration safety)

Status: Implemented - Custom drivers for Huawei and Ruijie, multi-vendor support with Cisco IOS, Huawei, and Ruijie tested

Architecture: Nornir best practice - host-level connection_options with single generic group

Unit Tests: 9/9 passing

Changelog:

  • 2026-03-13 (Evening): Added Ruijie enhanced driver and interactive command handling
    • Implemented RuijieTelnetEnhanced with hybrid batch/fallback strategy
    • Added automatic yes insertion for known interactive commands (router-id, erase, etc.)
    • Achieves ~2-3 seconds for 13 commands (vs 10+ seconds for one-by-one)
    • Auto-fallback to real-time detection for unknown interactive prompts
    • Registered gns3_ruijie_telnet device type
  • 2026-03-13 (Afternoon): Enhanced configuration safety and command handling
    • Added AAA/password configuration prohibition in system prompts
    • Implemented multi-line command expansion (_expand_multiline_commands())
    • Added device_type tag validation with error feedback
    • Prevents execution of commands that could lock users out of devices
    • Properly handles banner and other multi-line configuration commands
  • 2026-03-13 (Morning): Refactored to use host-level connection_options instead of dynamic groups
    • Removed _get_nornir_groups_config() and _get_nornir_group() helper functions
    • Simplified _initialize_nornir() to use single generic group
    • Updated get_gns3_device_port.py() to return host-level configuration
    • Reserved platform field for future NAPALM/scrapli plugin support
  • 2026-03-12: Initial implementation with custom Huawei driver