Your First Network Automation
Simulating Device Connections Safely
What you'll learn
- Why a simulated connection function is worth building deliberately, not just hardcoding mock results
- The exact seam where a real Netmiko connection will go in the next module
- How to write a function that returns realistic state, or raises a failure, for a given device
- Why the rest of the tool never needs to know whether a connection is simulated or real
- How to safely call the simulated connection and handle an unreachable device
Listen to a guided explanation of this lesson.
Spoken narration isn't supported in this browser.
The seam this whole project pivots on
Every device in this project's inventory has a hostname, an IP, and a platform — but none of that tells you whether SW2 is actually synced to NTP right now. Finding that out means connecting to the device. This module doesn't have a real one to connect to yet, so this lesson builds a simulated connection function: one function, with one job, that stands in for a real SSH session.
This isn't just a stopgap to make the lesson runnable. It's the single most important piece of how this project is structured — because it's exactly the piece that changes, and only that piece, when "Netmiko Fundamentals" connects this same project to a real device.
- Python automation
- simulate_connection()a function you control
- Mock device responsesafe, predictable, no device needed
- Python automation
- Netmikoa library that speaks SSH
- SSH
- Real network device
Nothing here gets thrown away
When the next module swaps simulate_connection() for a real Netmiko call, the inventory layer, the validation logic, the report format — everything you build in this module and the next two lessons — stays exactly as it is. Only the one function that answers "what's this device's state?" changes what's actually answering it. That's the whole point of building this seam deliberately, instead of scattering mock data throughout the script.
Writing the function
def simulate_connection(device):
"""
Stands in for a real SSH connection — this exact function is what
gets replaced with a real Netmiko call in the next module. Returns
the device's current NTP and VLAN compliance state, or raises
ConnectionError if the device can't be reached.
"""
simulated_state = {
"SW1": {"reachable": True, "ntp_synced": True, "vlan_compliant": True},
"SW2": {"reachable": True, "ntp_synced": False, "vlan_compliant": True},
"SW3": {"reachable": False, "ntp_synced": None, "vlan_compliant": None},
}
state = simulated_state.get(device["hostname"])
if state is None or not state["reachable"]:
raise ConnectionError(f"{device['hostname']} is unreachable")
return statesimulated_state is the only 'fake' part of this whole project — a small, honest lookup table standing in for what a real connection would discover. Everything else in this function (the .get(), the check, the raise) is exactly what a real version would do too.
Walk through what happens for each device: simulated_state.get(device["hostname"]) looks up that hostname's canned state — .get() returns None if the hostname isn't in the table at all, which the function treats the same as "not reachable." SW3's entry has "reachable": False, so even though it is in the table, the function still raises ConnectionError for it — exactly as a real connection attempt would if the switch were down.
Calling it safely
for device in valid_devices:
try:
state = simulate_connection(device)
print(f"{device['hostname']}: connected — NTP synced={state['ntp_synced']}, VLAN compliant={state['vlan_compliant']}")
except ConnectionError as e:
print(f"{device['hostname']}: {e}")Exactly the try/except pattern from 'Exceptions & Safe Automation,' now protecting a real call in a real project. One unreachable device (SW3) doesn't stop SW1 or the devices after it from being checked.
Key takeaways
- simulate_connection() is the seam this project is built around — the one function that gets replaced with real Netmiko/SSH next module.
- It returns the same kind of result a real connection would: a device's current state, or a ConnectionError if it can't be reached.
- .get() safely handles a missing hostname; checking reachable explicitly handles one that's present but down — together they cover both failure cases cleanly.
- The calling code (a loop with try/except) doesn't know or care whether the connection behind it is simulated or real — that's what makes the eventual swap safe and small.
- Nothing built in this module gets thrown away when a real device enters the picture — only this one function's insides change.