NetAuto Academy

Your First Network Automation

Simulating Device Connections Safely

Beginner13 min read

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
🎓 Teach This Lesson

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.

This module
  1. Python automation
  2. simulate_connection()a function you control
  3. Mock device responsesafe, predictable, no device needed
Netmiko Fundamentals (next)
  1. Python automation
  2. Netmikoa library that speaks SSH
  3. SSH
  4. 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.

Quick checkConcept check
What is simulate_connection() actually standing in for?

Writing the function

simulate_connection.py
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 state

simulated_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.

Quick checkIdentify the correct code
Which line correctly handles both cases — a hostname that's completely missing from simulated_state, and one that's present but marked unreachable?
Quick checkMyth check
Is building a dedicated simulate_connection() function extra, unnecessary work, compared to just hardcoding mock results directly wherever they're needed?

Calling it safely

check_devices.py
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.

Quick checkPredict output
Running the loop above against the three devices in this project's inventory, what does SW2's line print?
Quick checkScenario
Once 'Netmiko Fundamentals' replaces simulate_connection() with a real Netmiko-based function, does the try/except block shown above need to change?

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.