# Python Tools for Network Engineering

## SSH Management Libraries

1. **Netmiko**
   - Multi-vendor SSH connection handler with built-in support for 20+ vendors
   - Implements send_command(), send_config_set(), and send_config_from_file() methods
   - Handles credentials, connection timing, and error patterns
   - Example usage:
     ```python
     from netmiko import ConnectHandler
     device = {
         "device_type": "cisco_ios",
         "host": "192.168.1.1",
         "username": "admin",
         "password": "cisco"
     }
     with ConnectHandler(**device) as conn:
         output = conn.send_command("show ip int brief")
     ```

2. **Paramiko**
   - Low-level SSH2 protocol implementation in Python
   - Supports key-based authentication and proxy commands
   - Implementation example:
     ```python
     import paramiko
     client = paramiko.SSHClient()
     client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
     client.connect("192.168.1.1", username="admin", password="cisco")
     stdin, stdout, stderr = client.exec_command("show version")
     ```

## Configuration Management

1. **NAPALM**
   - Unified API across vendors (Cisco IOS/XR/NXOS, Juniper, Arista)
   - Key methods: get_facts(), get_interfaces(), merge_config(), replace_config()
   - Compare_config() method for configuration validation before commit
   - Implementation:
     ```python
     from napalm import get_network_driver
     driver = get_network_driver("ios")
     device = driver("192.168.1.1", "admin", "cisco")
     device.open()
     device.load_merge_candidate(filename="new_config.txt")
     diff = device.compare_config()
     if len(diff) > 0:
         device.commit_config()
     device.close()
     ```

2. **Nornir**
   - Concurrent task execution framework with inventory management
   - Python-native, no DSL required like Ansible
   - Plugins architecture for various connection methods
   - Example:
     ```python
     from nornir import InitNornir
     from nornir_netmiko import netmiko_send_command
     nr = InitNornir(config_file="config.yaml")
     result = nr.run(netmiko_send_command, command_string="show version")
     ```

## Protocol-Specific Libraries

1. **Scapy**
   - Full packet manipulation library supporting 400+ protocols
   - Packet creation, capture, and analysis capabilities
   - Built-in traceroute, ARP/DNS scanning, and route discovery
   - Example:
     ```python
     from scapy.all import IP, TCP, sr1
     packet = IP(dst="8.8.8.8")/TCP(dport=53, flags="S")
     response = sr1(packet, timeout=2)
     if response:
         print(response.summary())
     ```

2. **pyATS & Genie**
   - Test framework with 1200+ parsers for network device outputs
   - Stateful device modeling and configuration validation
   - Implementation:
     ```python
     from pyats.topology import loader
     from genie.testbed import load
     testbed = load('testbed.yaml')
     device = testbed.devices['router1']
     device.connect()
     output = device.parse("show interfaces")
     ```

## API and Protocol Interfaces

1. **Requests**
   - HTTP library supporting REST API interactions
   - JSON parsing and TLS/SSL verification
   - Example:
     ```python
     import requests
     url = "https://192.168.1.1/api/interfaces"
     headers = {"Content-Type": "application/json"}
     response = requests.get(url, headers=headers, auth=("admin", "cisco"), verify=False)
     interfaces = response.json()
     ```

2. **ncclient**
   - NETCONF protocol client library (RFC 6241)
   - XPath filtering support
   - Implementation:
     ```python
     from ncclient import manager
     with manager.connect(host="192.168.1.1", port=830, username="admin", 
                          password="cisco", hostkey_verify=False) as m:
         interfaces = m.get_config(source="running", filter=("subtree", 
                                   "<interfaces xmlns='urn:ietf:params:xml:ns:yang:ietf-interfaces'/>"))
     ```

3. **PyEZ (Juniper)**
   - Juniper-specific automation framework
   - RPC, configuration, and operational state methods
   - Example:
     ```python
     from jnpr.junos import Device
     from jnpr.junos.utils.config import Config
     dev = Device(host="192.168.1.1", user="admin", password="juniper")
     dev.open()
     cfg = Config(dev)
     cfg.load("set system services netconf ssh", format="set")
     if cfg.diff():
         cfg.commit()
     dev.close()
     ```

## Network Data Processing

1. **NetworkX**
   - Graph theory library for network topology analysis
   - Algorithms for path finding, centrality, and flow analysis
   - Supports GraphML, GML, and JSON formats
   - Implementation:
     ```python
     import networkx as nx
     G = nx.Graph()
     G.add_edge("Router1", "Router2", weight=10)
     G.add_edge("Router2", "Router3", weight=5)
     shortest_path = nx.shortest_path(G, "Router1", "Router3", weight="weight")
     ```

2. **Pandas**
   - Data analysis with DataFrame objects
   - Network inventory management and statistics
   - Example:
     ```python
     import pandas as pd
     interfaces_df = pd.DataFrame([
         {"device": "router1", "interface": "Gi0/1", "ip": "10.1.1.1", "status": "up"},
         {"device": "router1", "interface": "Gi0/2", "ip": "10.1.2.1", "status": "down"}
     ])
     # Filter interfaces that are down
     down_interfaces = interfaces_df[interfaces_df["status"] == "down"]
     ```

## Monitoring & Telemetry

1. **gRPC for Streaming Telemetry**
   - Bidirectional stream supporting dial-in and dial-out models
   - Protocol Buffers for efficient data encoding
   - Implementation:
     ```python
     import grpc
     from telemetry_pb2 import TelemetrySubscription
     from telemetry_pb2_grpc import TelemetryStub
     
     channel = grpc.insecure_channel('192.168.1.1:57500')
     stub = TelemetryStub(channel)
     subscription = TelemetrySubscription(path="Cisco-IOS-XR-infra-statsd-oper:infra-statistics/interfaces")
     for response in stub.CreateSub(subscription):
         process_telemetry(response)
     ```

2. **Prometheus Client**
   - Time-series metrics collection and exportation
   - Counter, Gauge, Histogram, and Summary metric types
   - Example:
     ```python
     from prometheus_client import start_http_server, Gauge
     import time
     
     interface_status = Gauge('interface_status', 'Interface operational status', ['device', 'interface'])
     
     def collect_metrics():
         # Logic to collect interface statuses from network devices
         interface_status.labels(device='router1', interface='Gi0/1').set(1)  # 1 = up
     
     if __name__ == '__main__':
         start_http_server(8000)
         while True:
             collect_metrics()
             time.sleep(60)
     ```

## YAML/JSON Parsing for Network Configurations

1. **PyYAML**
   - YAML parser and emitter
   - Used for configuration templates and data models
   - Implementation:
     ```python
     import yaml
     
     with open('network_inventory.yaml', 'r') as f:
         inventory = yaml.safe_load(f)
         
     for device in inventory['devices']:
         print(f"Connecting to {device['hostname']} at {device['ip']}")
     ```

2. **jq.py**
   - JSON querying tool similar to jq command-line utility
   - Complex data extraction capabilities
   - Example:
     ```python
     import jq
     
     with open('network_data.json', 'r') as f:
         data = f.read()
     
     # Extract all interfaces with admin status up
     result = jq.compile('.interfaces[] | select(.admin_status=="up") | .name').input(data).all()
     ```

---

# Network Engineering Python Workflows

## Network Discovery and Inventory Management

### Workflow 1: Automated Device Inventory Collection
**Scenario**: You need to maintain an accurate inventory of all network devices across multiple sites.

```python
from netmiko import ConnectHandler
import pandas as pd
import json

# Seed devices from which to discover the network
seed_devices = [
    {"ip": "10.1.1.1", "device_type": "cisco_ios", "username": "admin", "password": "cisco_pass"},
    {"ip": "10.2.1.1", "device_type": "cisco_ios", "username": "admin", "password": "cisco_pass"}
]

inventory = []

for device in seed_devices:
    try:
        with ConnectHandler(**device) as conn:
            # Get device facts
            hostname = conn.send_command("show hostname").strip()
            version = conn.send_command("show version | include Version")
            serial = conn.send_command("show inventory | include PID")
            
            # Get CDP neighbors to expand discovery
            neighbors_output = conn.send_command("show cdp neighbors detail")
            
            inventory.append({
                "hostname": hostname,
                "ip": device["ip"],
                "version": version,
                "serial": serial
            })
            
            # Parse CDP output to find new devices (implementation simplified)
            # Add new discovered devices to seed_devices list
            
    except Exception as e:
        print(f"Error connecting to {device['ip']}: {e}")

# Convert to DataFrame for analysis
df = pd.DataFrame(inventory)
df.to_csv("network_inventory.csv", index=False)
```

## Configuration Management

### Workflow 2: Compliance Verification and Remediation
**Scenario**: You need to verify that all access switches have proper security configurations and fix any violations.

```python
from nornir import InitNornir
from nornir_netmiko import netmiko_send_command, netmiko_send_config
from nornir.core.filter import F

# Initialize Nornir with inventory
nr = InitNornir(config_file="config.yaml")

# Filter only access switches
access_switches = nr.filter(F(role="access_switch"))

# Define compliance checks
security_commands = {
    "port_security": "show running-config | include switchport port-security",
    "bpdu_guard": "show spanning-tree summary | include BPDU Guard",
    "storm_control": "show running-config | include storm-control"
}

# Check compliance
results = {}
for check_name, command in security_commands.items():
    results[check_name] = access_switches.run(netmiko_send_command, command_string=command)

# Find non-compliant devices and apply fix
non_compliant = []
for device_name, device_results in results["port_security"].items():
    if "switchport port-security" not in device_results.result:
        non_compliant.append(device_name)

# Apply remediation to non-compliant devices
remediation_config = [
    "interface range GigabitEthernet 0/1-48",
    "switchport port-security",
    "switchport port-security maximum 3",
    "switchport port-security violation restrict",
    "spanning-tree bpduguard enable",
    "storm-control broadcast level 50.00"
]

# Apply fix only to non-compliant devices
non_compliant_devices = nr.filter(F(name__in=non_compliant))
remediation_results = non_compliant_devices.run(
    netmiko_send_config, config_commands=remediation_config
)

print(f"Fixed {len(non_compliant)} non-compliant devices")
```

## Network Monitoring and Troubleshooting

### Workflow 3: Automated Troubleshooting for Circuit Issues
**Scenario**: A WAN circuit is experiencing intermittent problems. You need to collect data continuously to identify patterns.

```python
import time
from netmiko import ConnectHandler
import pandas as pd
from datetime import datetime
import matplotlib.pyplot as plt

# Connect to the edge router
device = {
    "device_type": "cisco_ios",
    "host": "192.168.1.254",
    "username": "admin",
    "password": "cisco"
}

interface = "GigabitEthernet0/0"
monitoring_period = 3600  # 1 hour
interval = 30  # seconds
metrics = []

with ConnectHandler(**device) as conn:
    print(f"Monitoring {interface} for {monitoring_period/60} minutes...")
    
    start_time = time.time()
    end_time = start_time + monitoring_period
    
    while time.time() < end_time:
        # Collect interface statistics
        output = conn.send_command(f"show interface {interface}")
        
        # Parse output (simplified)
        input_rate = int(output.split("input rate ")[1].split(" ")[0])
        output_rate = int(output.split("output rate ")[1].split(" ")[0])
        input_errors = int(output.split("input errors, ")[0].split(" ")[-1])
        crc_errors = int(output.split("CRC, ")[1].split(" ")[0])
        
        # Collect ping statistics to provider edge
        ping_result = conn.send_command("ping 172.16.1.1 repeat 5")
        packet_loss = int(ping_result.split("%")[0].split(" ")[-1])
        
        # Store metrics
        metrics.append({
            "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
            "input_rate": input_rate,
            "output_rate": output_rate,
            "input_errors": input_errors,
            "crc_errors": crc_errors,
            "packet_loss": packet_loss
        })
        
        # Wait for next interval
        time.sleep(interval)

# Analyze collected data
df = pd.DataFrame(metrics)
df.to_csv("circuit_metrics.csv", index=False)

# Create time-series plots for analysis
plt.figure(figsize=(12, 8))
plt.subplot(3, 1, 1)
plt.plot(df["timestamp"], df["input_rate"], label="Input Rate")
plt.plot(df["timestamp"], df["output_rate"], label="Output Rate")
plt.legend()

plt.subplot(3, 1, 2)
plt.plot(df["timestamp"], df["crc_errors"], label="CRC Errors")
plt.legend()

plt.subplot(3, 1, 3)
plt.plot(df["timestamp"], df["packet_loss"], label="Packet Loss %")
plt.legend()

plt.tight_layout()
plt.savefig("circuit_analysis.png")
```

## Network Configuration Backup and Restoration

### Workflow 4: Automated Configuration Backup with Git Integration
**Scenario**: You need daily backups of all network device configurations with version control.

```python
from napalm import get_network_driver
import os
import json
from datetime import datetime
import subprocess
import yaml

# Load device inventory
with open("inventory.yaml", "r") as f:
    inventory = yaml.safe_load(f)

# Create backup directory structure
backup_dir = f"backups/{datetime.now().strftime('%Y-%m-%d')}"
os.makedirs(backup_dir, exist_ok=True)

# Backup configurations
for device in inventory["devices"]:
    try:
        # Initialize the appropriate driver
        driver = get_network_driver(device["os"])
        with driver(device["hostname"], device["username"], device["password"]) as device_conn:
            # Get running configuration
            running_config = device_conn.get_config()["running"]
            
            # Save to file
            device_file = f"{backup_dir}/{device['hostname']}.cfg"
            with open(device_file, "w") as f:
                f.write(running_config)
            
            print(f"Backed up {device['hostname']}")
    except Exception as e:
        print(f"Failed to backup {device['hostname']}: {e}")

# Git integration
try:
    # Initialize Git repo if not exists
    if not os.path.exists(os.path.join("backups", ".git")):
        subprocess.run(["git", "init"], cwd="backups")
    
    # Add all files
    subprocess.run(["git", "add", "."], cwd="backups")
    
    # Commit changes
    commit_message = f"Daily backup {datetime.now().strftime('%Y-%m-%d')}"
    subprocess.run(["git", "commit", "-m", commit_message], cwd="backups")
    
    # Push to remote (if configured)
    subprocess.run(["git", "push"], cwd="backups")
    
    print("Backup committed to git repository")
except Exception as e:
    print(f"Git operation failed: {e}")
```

## Network Change Management

### Workflow 5: Phased Network Change with Rollback
**Scenario**: You need to deploy a new routing protocol across the WAN with automatic rollback if connectivity tests fail.

```python
from nornir import InitNornir
from nornir_netmiko import netmiko_send_command, netmiko_send_config
from nornir.core.filter import F
import time

# Initialize Nornir
nr = InitNornir(config_file="config.yaml")

# Filter core routers for the first phase
core_routers = nr.filter(F(role="core_router"))

# Define change configuration
ospf_config = [
    "router ospf 100",
    "network 10.0.0.0 0.255.255.255 area 0",
    "redistribute connected subnets",
    "no auto-summary"
]

# Define validation tests
def validate_connectivity(task):
    # Test connectivity to critical hosts
    result = task.run(
        netmiko_send_command,
        command_string="ping 10.100.1.1 repeat 5"
    )
    
    # Check success criteria
    success = "Success rate is 100 percent" in result.result
    return success

# Phase 1: Deploy to core routers
print("Phase 1: Deploying to core routers...")
core_results = core_routers.run(netmiko_send_config, config_commands=ospf_config)

# Save configurations if successful
for host, result in core_results.items():
    if not result.failed:
        core_routers.filter(name=host).run(
            netmiko_send_command, command_string="write memory"
        )

# Validate core router changes
time.sleep(60)  # Allow for convergence
validation_results = []

for host in core_routers.inventory.hosts:
    success = core_routers.filter(name=host).run(task=validate_connectivity)[host][0].result
    validation_results.append(success)

# If validation fails, roll back
if not all(validation_results):
    print("Validation failed! Rolling back changes...")
    rollback_commands = [
        "no router ospf 100"
    ]
    core_routers.run(netmiko_send_config, config_commands=rollback_commands)
    print("Rollback completed.")
else:
    # Continue with Phase 2 (distribution routers)
    print("Phase 1 successful. Proceeding to Phase 2...")
    dist_routers = nr.filter(F(role="distribution_router"))
    # Similar implementation for Phase 2
```

## Network Capacity Planning

### Workflow 6: Interface Utilization Analysis for Capacity Planning
**Scenario**: You need to identify potential bottlenecks by analyzing interface utilization trends.

```python
import pandas as pd
import matplotlib.pyplot as plt
from netmiko import ConnectHandler
import time
from datetime import datetime, timedelta
import numpy as np

# Connect to core switch
device = {
    "device_type": "cisco_ios",
    "host": "10.0.1.1",
    "username": "admin",
    "password": "cisco_pass"
}

# Get list of uplink interfaces
with ConnectHandler(**device) as conn:
    interfaces_output = conn.send_command("show interface status | include uplink")
    uplink_interfaces = [line.split()[0] for line in interfaces_output.splitlines()]

# Collect utilization data over 24 hours (simplified - in real world use SNMP or streaming telemetry)
collection_period = 24 * 60 * 60  # 24 hours
interval = 300  # 5 minutes
data = []

with ConnectHandler(**device) as conn:
    start_time = time.time()
    end_time = start_time + collection_period
    
    # For demo purposes, we'll simulate with fewer iterations
    for _ in range(5):  # In production, use actual time-based loop
        timestamp = datetime.now()
        
        for interface in uplink_interfaces:
            output = conn.send_command(f"show interface {interface} | include rate")
            
            # Extract input and output utilization
            input_rate = int(output.split("input rate ")[1].split(" ")[0])
            output_rate = int(output.split("output rate ")[1].split(" ")[0])
            
            # Convert to Mbps
            input_mbps = input_rate * 8 / 1000000
            output_mbps = output_rate * 8 / 1000000
            
            data.append({
                "timestamp": timestamp,
                "interface": interface,
                "input_mbps": input_mbps,
                "output_mbps": output_mbps
            })
        
        # In real implementation, wait for next interval
        # time.sleep(interval)

# Convert to DataFrame
df = pd.DataFrame(data)

# Analysis
utilization_summary = df.groupby("interface").agg({
    "input_mbps": ["mean", "max", "std"],
    "output_mbps": ["mean", "max", "std"]
})

# Find interfaces with high utilization (>70% of capacity)
interface_capacity = {
    "TenGigabitEthernet1/1": 10000,  # 10G
    "FortyGigabitEthernet2/1": 40000  # 40G
}

for interface, capacity in interface_capacity.items():
    if interface in utilization_summary.index:
        max_util = max(
            utilization_summary.loc[interface, ("input_mbps", "max")],
            utilization_summary.loc[interface, ("output_mbps", "max")]
        )
        utilization_pct = (max_util / capacity) * 100
        
        if utilization_pct > 70:
            print(f"WARNING: {interface} reached {utilization_pct:.1f}% utilization")
            
            # Forecast when 90% will be reached based on trend
            interface_data = df[df["interface"] == interface]
            
            # Time series forecast (simplified linear regression)
            x = np.array(range(len(interface_data)))
            y = interface_data["output_mbps"].values
            
            # Fit line to forecast trend
            z = np.polyfit(x, y, 1)
            slope = z[0]
            
            # Calculate days until 90% capacity
            if slope > 0:
                mbps_until_90pct = 0.9 * capacity - max_util
                days_until_90pct = mbps_until_90pct / (slope * 288)  # 288 = samples per day
                print(f"  Forecast: Will reach 90% capacity in {days_until_90pct:.1f} days")
```

## Network Security Automation

### Workflow 7: Automated ACL Audit and Remediation
**Scenario**: You need to audit all access control lists to ensure compliance with security policies.

```python
from napalm import get_network_driver
import re
import pandas as pd
import yaml

# Load security policy rules
with open("security_policy.yaml", "r") as f:
    security_policy = yaml.safe_load(f)

# Load device inventory
with open("inventory.yaml", "r") as f:
    inventory = yaml.safe_load(f)

# Define non-compliant patterns
dangerous_patterns = [
    r"permit ip any any",              # Too permissive
    r"permit tcp any any eq 23",       # Telnet access
    r"permit tcp any any eq 21",       # FTP access
    r"remark temporary|remark TEMPORARY"  # Temporary rules
]

results = []

# Audit ACLs on each device
for device in inventory["devices"]:
    try:
        driver = get_network_driver(device["os"])
        with driver(device["hostname"], device["username"], device["password"]) as device_conn:
            # Get ACLs from device
            acls = device_conn.get_acl()
            
            for acl_name, acl_details in acls.items():
                for rule_num, rule in acl_details.items():
                    if isinstance(rule, dict) and "access_list" in rule:
                        acl_line = rule["access_list"]
                        
                        # Check against dangerous patterns
                        for pattern in dangerous_patterns:
                            if re.search(pattern, acl_line):
                                results.append({
                                    "device": device["hostname"],
                                    "acl_name": acl_name,
                                    "rule_number": rule_num,
                                    "rule_text": acl_line,
                                    "violation": pattern,
                                    "action": "Remove or restrict"
                                })
                        
                        # Check for required rules (simplified)
                        for required_rule in security_policy["required_rules"]:
                            if required_rule["acl"] == acl_name and required_rule["rule"] not in acl_line:
                                # Note: This is simplified. In practice, need more sophisticated rule comparison
                                results.append({
                                    "device": device["hostname"],
                                    "acl_name": acl_name,
                                    "rule_number": "N/A",
                                    "rule_text": "Missing required rule",
                                    "violation": f"Missing: {required_rule['rule']}",
                                    "action": "Add required rule"
                                })
    
    except Exception as e:
        print(f"Error auditing {device['hostname']}: {e}")

# Create audit report
audit_df = pd.DataFrame(results)
audit_df.to_csv("acl_audit_results.csv", index=False)
print(f"Found {len(results)} ACL compliance issues")

# Generate remediation commands (example for a specific device)
if results:
    remediation_commands = {}
    
    for issue in results:
        device = issue["device"]
        if device not in remediation_commands:
            remediation_commands[device] = []
        
        if "Remove" in issue["action"]:
            remediation_commands[device].append(f"ip access-list extended {issue['acl_name']}")
            remediation_commands[device].append(f"no {issue['rule_number']}")
        elif "Add required rule" in issue["action"]:
            # Find the corresponding required rule
            for required_rule in security_policy["required_rules"]:
                if required_rule["acl"] == issue["acl_name"]:
                    remediation_commands[device].append(f"ip access-list extended {issue['acl_name']}")
                    remediation_commands[device].append(required_rule["rule"])
    
    # Print remediation commands for review
    for device, commands in remediation_commands.items():
        print(f"\nRemediation commands for {device}:")
        for cmd in commands:
            print(f"  {cmd}")
```

## Network API Automation

### Workflow 8: Automating Cloud Network Configuration via APIs
**Scenario**: You need to provision network resources in a cloud environment based on your on-premises configurations.

```python
import requests
import json
import yaml
import time
from netmiko import ConnectHandler

# Load on-premises VLANs that need to be mirrored in cloud
with ConnectHandler(device_type="cisco_ios", host="10.1.1.1", 
                   username="admin", password="cisco_pass") as conn:
    vlan_output = conn.send_command("show vlan brief")

# Parse VLANs (simplified)
vlans = []
for line in vlan_output.splitlines()[2:]:  # Skip header lines
    if line.strip():
        parts = line.split()
        if parts[0].isdigit() and int(parts[0]) < 1000:  # Filter standard VLANs
            vlan_id = parts[0]
            vlan_name = parts[1]
            vlans.append({"id": vlan_id, "name": vlan_name})

# Load cloud provider API configuration
with open("cloud_config.yaml", "r") as f:
    cloud_config = yaml.safe_load(f)

# Set up API connection
api_url = cloud_config["api_url"]
headers = {
    "Authorization": f"Bearer {cloud_config['api_key']}",
    "Content-Type": "application/json"
}

# Get existing cloud VPCs/VNets
response = requests.get(f"{api_url}/networks", headers=headers)
cloud_networks = response.json()["networks"]

# Find our target VPC/VNet
target_vpc = next((n for n in cloud_networks if n["name"] == cloud_config["vpc_name"]), None)

if not target_vpc:
    print(f"VPC {cloud_config['vpc_name']} not found!")
    exit(1)

# Provision subnets in cloud that match our on-prem VLANs
vpc_id = target_vpc["id"]
existing_subnets_response = requests.get(f"{api_url}/networks/{vpc_id}/subnets", headers=headers)
existing_subnets = existing_subnets_response.json()["subnets"]
existing_subnet_names = [s["name"] for s in existing_subnets]

# Calculate subnet CIDR blocks (example logic)
base_cidr = "10.100.0.0/16"  # Cloud VPC CIDR
subnet_index = 0

for vlan in vlans:
    subnet_name = f"subnet-{vlan['name']}"
    
    # Skip if subnet already exists
    if subnet_name in existing_subnet_names:
        print(f"Subnet {subnet_name} already exists. Skipping.")
        continue
    
    # Calculate subnet CIDR (simplified)
    subnet_cidr = f"10.100.{subnet_index}.0/24"
    subnet_index += 1
    
    # Create subnet via API
    subnet_data = {
        "name": subnet_name,
        "cidr_block": subnet_cidr,
        "vpc_id": vpc_id,
        "tags": {
            "corresponds_to_vlan": vlan["id"],
            "environment": "production"
        }
    }
    
    try:
        response = requests.post(
            f"{api_url}/networks/{vpc_id}/subnets",
            headers=headers,
            data=json.dumps(subnet_data)
        )
        
        if response.status_code == 201:
            print(f"Created subnet {subnet_name} with CIDR {subnet_cidr}")
        else:
            print(f"Failed to create subnet {subnet_name}: {response.text}")
    
    except Exception as e:
        print(f"API error: {e}")
    
    # Rate limit API calls
    time.sleep(1)

# Setup VPC peering between on-prem and cloud (via API)
peering_data = {
    "name": "on-prem-to-cloud-peer",
    "vpc_id": vpc_id,
    "peer_vpc_id": cloud_config["on_prem_gateway_id"],
    "auto_accept": True
}

try:
    response = requests.post(
        f"{api_url}/vpc-peerings",
        headers=headers,
        data=json.dumps(peering_data)
    )
    
    if response.status_code == 201:
        print("VPC peering connection established")
        
        # Update on-prem router with routes to cloud subnets
        with ConnectHandler(device_type="cisco_ios", host="10.1.1.1", 
                           username="admin", password="cisco_pass") as conn:
            
            # Add static routes to cloud subnets
            conn.send_config_set([
                f"ip route 10.100.0.0 255.255.0.0 {cloud_config['vpn_endpoint']}"
            ])
            
            print("Updated on-premises router with cloud routes")
    else:
        print(f"Failed to establish VPC peering: {response.text}")

except Exception as e:
    print(f"API error during peering: {e}")
```

These workflows showcase practical applications of Python for network engineering tasks spanning discovery, configuration management, monitoring, backup, change management, capacity planning, security auditing, and cloud integration.