CML_OSPF_Basic_conf.py

"""
Create a CML 2.x lab with 3 routers in a triangle topology, all running OSPF area 0.

Requirements:
    pip install virl2-client

Credentials — set these in the environment or edit the constants below:
    CML_URL       e.g. https://cml.example.com
    CML_USERNAME
    CML_PASSWORD

Topology:

        R1 (1.1.1.1)
        /          \\
 10.0.12.0/30   10.0.13.0/30
      /              \\
   R2 (2.2.2.2) --- R3 (3.3.3.3)
          10.0.23.0/30

All links in OSPF area 0. Loopback0 on each router advertised into OSPF.
"""

import os
from virl2_client import ClientLibrary

# ---------- Connection settings ----------
CML_URL      = os.getenv("CML_URL",      "https://cml.example.com")
CML_USERNAME = os.getenv("CML_USERNAME", "admin")
CML_PASSWORD = os.getenv("CML_PASSWORD", "changeme")

# ---------- Router day-0 configs ----------
# Interface map per router:
#   Gi0/0 — unused (mgmt/console)
#   Gi0/1 — first peer
#   Gi0/2 — second peer

R1_CONFIG = """\
hostname R1
!
interface Loopback0
 ip address 1.1.1.1 255.255.255.255
!
interface GigabitEthernet0/1
 description to R2
 ip address 10.0.12.1 255.255.255.252
 no shutdown
!
interface GigabitEthernet0/2
 description to R3
 ip address 10.0.13.1 255.255.255.252
 no shutdown
!
router ospf 1
 router-id 1.1.1.1
 network 1.1.1.1 0.0.0.0 area 0
 network 10.0.12.0 0.0.0.3 area 0
 network 10.0.13.0 0.0.0.3 area 0
!
end
"""

R2_CONFIG = """\
hostname R2
!
interface Loopback0
 ip address 2.2.2.2 255.255.255.255
!
interface GigabitEthernet0/1
 description to R1
 ip address 10.0.12.2 255.255.255.252
 no shutdown
!
interface GigabitEthernet0/2
 description to R3
 ip address 10.0.23.1 255.255.255.252
 no shutdown
!
router ospf 1
 router-id 2.2.2.2
 network 2.2.2.2 0.0.0.0 area 0
 network 10.0.12.0 0.0.0.3 area 0
 network 10.0.23.0 0.0.0.3 area 0
!
end
"""

R3_CONFIG = """\
hostname R3
!
interface Loopback0
 ip address 3.3.3.3 255.255.255.255
!
interface GigabitEthernet0/1
 description to R2
 ip address 10.0.23.2 255.255.255.252
 no shutdown
!
interface GigabitEthernet0/2
 description to R1
 ip address 10.0.13.2 255.255.255.252
 no shutdown
!
router ospf 1
 router-id 3.3.3.3
 network 3.3.3.3 0.0.0.0 area 0
 network 10.0.23.0 0.0.0.3 area 0
 network 10.0.13.0 0.0.0.3 area 0
!
end
"""


def ensure_interfaces(node, count: int):
    """Make sure the node has at least `count` physical interfaces (slots 0..count-1)."""
    while len(node.physical_interfaces) < count:
        node.create_interface()


def main():
    # Connect
    client = ClientLibrary(
        CML_URL,
        CML_USERNAME,
        CML_PASSWORD,
        ssl_verify=False,   # set True in production
    )
    client.is_system_ready(wait=True)

    # Create lab
    lab = client.create_lab(title="3-Router OSPF Triangle")
    print(f"Created lab '{lab.title}' (id={lab.id})")

    # Create nodes — "iosv" is the small Cisco IOSv router (good enough for OSPF)
    r1 = lab.create_node("R1", "iosv", x=-200, y=-100)
    r2 = lab.create_node("R2", "iosv", x= 200, y=-100)
    r3 = lab.create_node("R3", "iosv", x=   0, y= 150)

    # Push day-0 configs
    r1.configuration = R1_CONFIG
    r2.configuration = R2_CONFIG
    r3.configuration = R3_CONFIG

    # Make sure each router has Gi0/0, Gi0/1, Gi0/2 (slots 0,1,2)
    for node in (r1, r2, r3):
        ensure_interfaces(node, 3)

    # Wire the triangle. Slot index matches Gi0/N.
    #   R1 Gi0/1  <-> R2 Gi0/1   (10.0.12.0/30)
    #   R2 Gi0/2  <-> R3 Gi0/1   (10.0.23.0/30)
    #   R1 Gi0/2  <-> R3 Gi0/2   (10.0.13.0/30)
    lab.create_link(r1.physical_interfaces[1], r2.physical_interfaces[1])
    lab.create_link(r2.physical_interfaces[2], r3.physical_interfaces[1])
    lab.create_link(r1.physical_interfaces[2], r3.physical_interfaces[2])

    print("Nodes and links created. Starting lab...")
    lab.start()
    print("Lab is running.")
    print()
    print("Verify once routers boot (~1-2 min):")
    print("  R1# show ip ospf neighbor")
    print("  R1# show ip route ospf")
    print("  R1# ping 3.3.3.3 source 1.1.1.1")


if __name__ == "__main__":
    main()