mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-09-24 21:00:24 +03:00
practice for multi-vendor device support
Refactor Nornir configuration to use host-level connection_options
instead of dynamic groups, following Nornir's configuration priority
model (host > group > defaults).
**Problem:**
Previous implementation used first device's configuration for all
devices, causing Cisco devices to use Huawei driver and vice versa.
**Solution:**
- Each host now has device-specific connection_options at host level
- Single generic "network_devices" group for shared settings
- Host-level config automatically overrides group-level config
**Changes:**
- Remove: _get_nornir_groups_config() helper function
- Remove: _get_nornir_group() helper function
- Simplify: _initialize_nornir() to use single generic group
- Update: get_gns3_device_port.py() to return host-level config
- Reserve: platform field for future NAPALM/scrapli plugin support
**Benefits:**
- Cleaner code structure (no dynamic group creation)
- Follows Nornir best practice ("configuration proximity")
- Easy to extend with new device types
- Properly handles mixed-vendor topologies
689 lines
21 KiB
Markdown
689 lines
21 KiB
Markdown
# 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) |
|
|
|
|
## 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**
|
|
```python
|
|
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**
|
|
```python
|
|
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**
|
|
```python
|
|
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:
|
|
|
|
```python
|
|
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**
|
|
```python
|
|
# Automatically runs on module import
|
|
try:
|
|
register_custom_device_type()
|
|
except Exception as e:
|
|
logger.warning(f"Failed to register custom device type: {e}")
|
|
```
|
|
|
|
## 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` |
|
|
|
|
### Nornir Best Practice: Host-Level Connection Configuration
|
|
|
|
The system uses **Nornir's configuration priority** (host > group > defaults) to handle multi-vendor environments efficiently:
|
|
|
|
```python
|
|
# 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):**
|
|
```python
|
|
# 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):**
|
|
```python
|
|
# 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)
|
|
|
|
```python
|
|
# 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)
|
|
|
|
```python
|
|
# 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)
|
|
|
|
```python
|
|
# 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
|
|
|
|
```python
|
|
# 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):**
|
|
```python
|
|
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):**
|
|
```python
|
|
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
|
|
|
|
```python
|
|
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
|
|
|
|
```python
|
|
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
|
|
│ │ ├── 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
|
|
├── 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
|
|
```
|
|
|
|
**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`
|
|
|
|
## Unit Testing
|
|
|
|
### Test Coverage
|
|
|
|
```python
|
|
# 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:**
|
|
```bash
|
|
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.
|
|
|
|
## Related Documentation
|
|
|
|
- [Custom Netmiko README](../../../../../gns3server/agent/gns3_copilot/utils/custom_netmiko/README.md) - Driver development guide
|
|
- [Node Control Tools](./node-control-tools.md) - Device lifecycle management
|
|
- [Command Security](./command-security.md) - Command filtering and validation
|
|
- [Chat API](./chat-api.md) - Session management and SSE
|
|
|
|
## References
|
|
|
|
- [Netmiko Documentation](https://ktbyers.github.io/netmiko/)
|
|
- [Netmiko PLATFORMS.md](https://github.com/ktbyers/netmiko/blob/master/PLATFORMS.md)
|
|
- [Nornir Documentation](https://nornir.readthedocs.io/)
|
|
|
|
---
|
|
|
|
_Implementation Date: 2026-03-12_
|
|
|
|
_Last Updated: 2026-03-13 (Architecture refactored to host-level configuration)_
|
|
|
|
_Status: ✅ Implemented - Custom Huawei driver for GNS3 emulation, multi-vendor support with Cisco IOS and Huawei tested_
|
|
|
|
_Architecture: Nornir best practice - host-level connection_options with single generic group_
|
|
|
|
_Unit Tests: ✅ 9/9 passing_
|
|
|
|
_Changelog:_
|
|
- **2026-03-13**: 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
|