├── .drone.yml ├── .github └── workflows │ └── github-actions-demo.yml ├── .gitignore ├── LICENSE ├── README.md ├── backup.py ├── build.py ├── configs ├── config.yaml ├── groups.yaml └── hosts.yaml ├── post_check └── hosts │ ├── host1.json │ └── host4.json ├── requirements.txt ├── snapshots └── configs │ ├── pdx-rtr-eos-01.txt │ ├── pdx-rtr-eos-02.txt │ ├── pdx-rtr-eos-03.txt │ └── pdx-rtr-eos-04.txt ├── suzieq-cfg.yml ├── test.py ├── test_suzieq.py └── tools.py /.drone.yml: -------------------------------------------------------------------------------- 1 | kind: pipeline 2 | type: docker 3 | name: Testing Python CI/CD 4 | 5 | 6 | steps: 7 | - name: Black Code Format Check 8 | image: juliopdx/netauto 9 | commands: 10 | - black . --check 11 | 12 | - name: Batfish Prechecks 13 | image: juliopdx/netauto 14 | commands: 15 | - python test.py 16 | 17 | - name: Precheck Configuration Diff 18 | image: juliopdx/netauto 19 | environment: 20 | MY_SECRET: 21 | from_secret: MY_SECRET 22 | commands: 23 | - python build.py --dry_run 24 | 25 | - name: Deploy Configurations 26 | image: juliopdx/netauto 27 | environment: 28 | MY_SECRET: 29 | from_secret: MY_SECRET 30 | commands: 31 | - python build.py --no_dry_run 32 | when: 33 | branch: 34 | - master 35 | - main 36 | 37 | - name: Suzieq Check 38 | image: python:3.8 39 | commands: 40 | - pip install suzieq rich 41 | - python test_suzieq.py 42 | when: 43 | branch: 44 | - master 45 | - main 46 | volumes: 47 | - name: suzieq 48 | path: /tmp/suz 49 | 50 | volumes: 51 | - name: suzieq 52 | host: 53 | path: /home/juliopdx/suz 54 | 55 | trigger: 56 | event: 57 | exclude: 58 | - pull_request -------------------------------------------------------------------------------- /.github/workflows/github-actions-demo.yml: -------------------------------------------------------------------------------- 1 | # name: GitHub Actions Demo 2 | # on: [push] 3 | # jobs: 4 | # container-job: 5 | # runs-on: self-hosted 6 | # container: juliopdx/netauto 7 | # steps: 8 | # - run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event." 9 | # - run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by GitHub!" 10 | # - run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}." 11 | # - name: Check out repository code 12 | # uses: actions/checkout@v2 13 | # - run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner." 14 | # - run: echo "🖥️ The workflow is now ready to test your code on the runner." 15 | # - name: List files in the repository 16 | # run: | 17 | # ls ${{ github.workspace }} 18 | # - name: Install Dependencies 19 | # run: black . --check 20 | # - run: echo "🍏 This job's status is ${{ job.status }}." 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | venv 2 | __pycache 3 | .pytest_cache 4 | *.pyc 5 | nornir.log -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 JulioPDX 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Network CI/CD Pipeline Proof of Concept 2 | 3 | I learend so much building out this POC and I want to thank everyone that has checked out the blog series that references this code base. I hope you brave souls that have started building your own can use some of these tools or concepts in your future deployments. Please check out the blog posts where I go into detail about every phase! 4 | 5 | - [Building a Network CI/CD Pipeline Part 1 - Installing Docker](https://juliopdx.com/2021/10/20/building-a-network-ci/cd-pipeline-part-1/) 6 | - [Building a Network CI/CD Pipeline Part 2 - Installing Drone Server and Runner](https://juliopdx.com/2021/10/20/building-a-network-ci/cd-pipeline-part-2/) 7 | - [Building a Network CI/CD Pipeline Part 3 - .drone.yml and Building a Docker Image](https://juliopdx.com/2021/10/20/building-a-network-ci/cd-pipeline-part-3/) 8 | - [Building a Network CI/CD Pipeline Part 4 - Testing with Batfish](https://juliopdx.com/2021/10/31/building-a-network-ci/cd-pipeline-part-4/) 9 | - [Building a Network CI/CD Pipeline Part 5 - Deployments with Nornir and NAPALM](https://juliopdx.com/2021/11/08/building-a-network-ci/cd-pipeline-part-5/) 10 | - [Building a Network CI/CD Pipeline Part 6 - Post Tests with Suzieq](https://juliopdx.com/2021/11/12/building-a-network-ci/cd-pipeline-part-6/) 11 | -------------------------------------------------------------------------------- /backup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """Simple script used for backups""" 4 | 5 | import os 6 | import napalm 7 | 8 | 9 | PASS = os.getenv("MY_PASS") 10 | 11 | devices = [ 12 | { 13 | "hostname": "pdx-rtr-eos-01", 14 | "device_type": "eos", 15 | "host": "192.168.10.121", 16 | "username": "admin", 17 | "password": PASS, 18 | }, 19 | { 20 | "hostname": "pdx-rtr-eos-02", 21 | "device_type": "eos", 22 | "host": "192.168.10.122", 23 | "username": "admin", 24 | "password": PASS, 25 | }, 26 | { 27 | "hostname": "pdx-rtr-eos-03", 28 | "device_type": "eos", 29 | "host": "192.168.10.123", 30 | "username": "admin", 31 | "password": PASS, 32 | }, 33 | { 34 | "hostname": "pdx-rtr-eos-04", 35 | "device_type": "eos", 36 | "host": "192.168.10.124", 37 | "username": "admin", 38 | "password": PASS, 39 | }, 40 | ] 41 | 42 | 43 | for device in devices: 44 | driver = napalm.get_network_driver(device["device_type"]) 45 | temp_device = driver( 46 | hostname=device["host"], 47 | username=device["username"], 48 | password=device["password"], 49 | ) 50 | temp_device.open() 51 | config = temp_device.get_config(retrieve="running") 52 | run_conf = config["running"] 53 | 54 | ### create file with running config in backup_config folder 55 | with open( 56 | f"./snapshots/configs/{device['hostname']}.txt", "w", encoding="utf-8" 57 | ) as file: 58 | file.write(run_conf) 59 | temp_device.close() 60 | -------------------------------------------------------------------------------- /build.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """Script used to configure the network""" 4 | 5 | import argparse 6 | from nornir import InitNornir 7 | from nornir_napalm.plugins.tasks import napalm_configure 8 | from nornir_utils.plugins.functions import print_result 9 | from tools import nornir_set_creds 10 | 11 | 12 | parser = argparse.ArgumentParser() 13 | parser.add_argument( 14 | "--dry_run", dest="dry", action="store_true", help="Will not run on devices" 15 | ) 16 | parser.add_argument( 17 | "--no_dry_run", dest="dry", action="store_false", help="Will run on devices" 18 | ) 19 | parser.set_defaults(dry=True) 20 | args = parser.parse_args() 21 | 22 | 23 | def deploy_network(task): 24 | """Configures network with NAPALM""" 25 | task1_result = task.run( 26 | name=f"Configuring {task.host.name}!", 27 | task=napalm_configure, 28 | filename=f"./snapshots/configs/{task.host.name}.txt", 29 | dry_run=args.dry, 30 | replace=True, 31 | ) 32 | 33 | 34 | def main(): 35 | """Used to run all the things""" 36 | norn = InitNornir(config_file="configs/config.yaml", core={"raise_on_error": True}) 37 | nornir_set_creds(norn) 38 | result = norn.run(task=deploy_network) 39 | print_result(result) 40 | 41 | 42 | if __name__ == "__main__": 43 | main() 44 | -------------------------------------------------------------------------------- /configs/config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | inventory: 3 | plugin: SimpleInventory 4 | options: 5 | host_file: "./configs/hosts.yaml" 6 | group_file: "./configs/groups.yaml" 7 | runner: 8 | plugin: threaded 9 | options: 10 | num_workers: 10 -------------------------------------------------------------------------------- /configs/groups.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | eos: 3 | platform: eos -------------------------------------------------------------------------------- /configs/hosts.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | pdx-rtr-eos-01: 3 | hostname: 192.168.10.121 4 | groups: 5 | - eos 6 | 7 | pdx-rtr-eos-02: 8 | hostname: 192.168.10.122 9 | groups: 10 | - eos 11 | 12 | pdx-rtr-eos-03: 13 | hostname: 192.168.10.123 14 | groups: 15 | - eos 16 | 17 | pdx-rtr-eos-04: 18 | hostname: 192.168.10.124 19 | groups: 20 | - eos -------------------------------------------------------------------------------- /post_check/hosts/host1.json: -------------------------------------------------------------------------------- 1 | { 2 | "hostname" : "VPC1", 3 | "hostInterfaces" : { 4 | "eth0" : { 5 | "name": "eth0", 6 | "prefix" : "192.168.1.2/24", 7 | "gateway": "192.168.1.1" 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /post_check/hosts/host4.json: -------------------------------------------------------------------------------- 1 | { 2 | "hostname" : "VPC4", 3 | "hostInterfaces" : { 4 | "eth0" : { 5 | "name": "eth0", 6 | "prefix" : "192.168.4.2/24", 7 | "gateway": "192.168.4.1" 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | netmiko 2 | rich 3 | black 4 | pybatfish 5 | napalm 6 | pytest 7 | nornir 8 | nornir-utils 9 | nornir-napalm -------------------------------------------------------------------------------- /snapshots/configs/pdx-rtr-eos-01.txt: -------------------------------------------------------------------------------- 1 | ! Command: show running-config 2 | ! device: pdx-rtr-eos-01 (vEOS-lab, EOS-4.27.0F) 3 | ! 4 | ! boot system flash:/vEOS-lab.swi 5 | ! 6 | no aaa root 7 | ! 8 | username admin privilege 15 role network-admin secret sha512 $6$rrKbLqxCZPj90dQ9$KphrLiR43VJMZtyo7e.o6ZdjNKClXMJ4gGdck.S6bbcu6svj/vcKoGuC/fPTbt5gkSugYumPY47O5yMc6d7sc/ 9 | username suzie privilege 15 role network-admin secret sha512 $6$6zV.qw9F48hskMAL$DW01gDlj1MQbpeBfcLvN.wTVt.j1TTuDd1f7c91n6k5TDx41v5QNsdb7GAO9JyTZy4h1r3yA70Gfe8Jb4dBm01 10 | ! 11 | transceiver qsfp default-mode 4x10G 12 | ! 13 | service routing protocols model ribd 14 | ! 15 | hostname pdx-rtr-eos-01 16 | ! 17 | spanning-tree mode mstp 18 | ! 19 | vrf instance MGMT 20 | ! 21 | management api http-commands 22 | no shutdown 23 | ! 24 | vrf MGMT 25 | no shutdown 26 | ! 27 | interface Ethernet1 28 | description connection to pdx-rtr-eos-022 29 | no switchport 30 | ip address 10.0.12.1/24 31 | ip ospf network point-to-point 32 | ip ospf area 0.0.0.0 33 | ! 34 | interface Ethernet2 35 | no switchport 36 | ip address 192.168.1.1/24 37 | ip ospf area 0.0.0.0 38 | ! 39 | interface Ethernet3 40 | ! 41 | interface Ethernet4 42 | ! 43 | interface Ethernet5 44 | ! 45 | interface Ethernet6 46 | ! 47 | interface Ethernet7 48 | ! 49 | interface Ethernet8 50 | ! 51 | interface Loopback1 52 | ip address 10.0.0.1/32 53 | ip ospf area 0.0.0.0 54 | ! 55 | interface Management1 56 | description MGMT 57 | vrf MGMT 58 | ip address 192.168.10.121/24 59 | ! 60 | ip routing 61 | no ip routing vrf MGMT 62 | ! 63 | ip route vrf MGMT 0.0.0.0/0 192.168.10.1 64 | ! 65 | router bgp 65001 66 | router-id 10.0.0.1 67 | timers bgp 10 30 68 | neighbor 10.0.0.4 remote-as 65004 69 | neighbor 10.0.0.4 update-source Loopback1 70 | neighbor 10.0.0.4 ebgp-multihop 3 71 | ! 72 | router ospf 1 73 | router-id 10.0.0.1 74 | passive-interface Ethernet2 75 | passive-interface Loopback1 76 | max-lsa 12000 77 | ! 78 | end 79 | -------------------------------------------------------------------------------- /snapshots/configs/pdx-rtr-eos-02.txt: -------------------------------------------------------------------------------- 1 | ! Command: show running-config 2 | ! device: pdx-rtr-eos-02 (vEOS-lab, EOS-4.27.0F) 3 | ! 4 | ! boot system flash:/vEOS-lab.swi 5 | ! 6 | no aaa root 7 | ! 8 | username admin privilege 15 role network-admin secret sha512 $6$Kl2aP7aosClWdG9S$2CS1H8LQoedChmX5TtFx0VBkFNE.hymxJ2CsWWxKfdFVIqZudoqVennR00FiyBH1UGc27rpZmMC28JI8sSpD.. 9 | username suzie privilege 15 role network-admin secret sha512 $6$6zV.qw9F48hskMAL$DW01gDlj1MQbpeBfcLvN.wTVt.j1TTuDd1f7c91n6k5TDx41v5QNsdb7GAO9JyTZy4h1r3yA70Gfe8Jb4dBm01 10 | ! 11 | transceiver qsfp default-mode 4x10G 12 | ! 13 | service routing protocols model ribd 14 | ! 15 | hostname pdx-rtr-eos-02 16 | ! 17 | spanning-tree mode mstp 18 | ! 19 | vrf instance MGMT 20 | ! 21 | management api http-commands 22 | no shutdown 23 | ! 24 | vrf MGMT 25 | no shutdown 26 | ! 27 | interface Ethernet1 28 | description connection to pdx-rtr-eos-01 29 | no switchport 30 | ip address 10.0.12.2/24 31 | ip ospf network point-to-point 32 | ip ospf area 0.0.0.0 33 | ! 34 | interface Ethernet2 35 | description connection to pdx-rtr-eos-03 36 | no switchport 37 | ip address 10.0.23.2/24 38 | ip ospf network point-to-point 39 | ip ospf area 0.0.0.0 40 | ! 41 | interface Ethernet3 42 | ! 43 | interface Ethernet4 44 | ! 45 | interface Ethernet5 46 | ! 47 | interface Ethernet6 48 | ! 49 | interface Ethernet7 50 | ! 51 | interface Ethernet8 52 | ! 53 | interface Management1 54 | vrf MGMT 55 | ip address 192.168.10.122/24 56 | ! 57 | ip routing 58 | no ip routing vrf MGMT 59 | ! 60 | ip route vrf MGMT 0.0.0.0/0 192.168.10.1 61 | ! 62 | router ospf 1 63 | router-id 10.0.0.2 64 | max-lsa 12000 65 | ! 66 | end 67 | -------------------------------------------------------------------------------- /snapshots/configs/pdx-rtr-eos-03.txt: -------------------------------------------------------------------------------- 1 | ! Command: show running-config 2 | ! device: pdx-rtr-eos-03 (vEOS-lab, EOS-4.27.0F) 3 | ! 4 | ! boot system flash:/vEOS-lab.swi 5 | ! 6 | no aaa root 7 | ! 8 | username admin privilege 15 role network-admin secret sha512 $6$pH.laHZuhmfihqj8$gq0Lm3Gihocxlvc6Lg3g.HlcXlCMxy2BV6.SHdxqb5OrSzmui2iPOkhtQZbA6UiA2WUyzemSbI.M4k0ghY522/ 9 | username suzie privilege 15 role network-admin secret sha512 $6$6zV.qw9F48hskMAL$DW01gDlj1MQbpeBfcLvN.wTVt.j1TTuDd1f7c91n6k5TDx41v5QNsdb7GAO9JyTZy4h1r3yA70Gfe8Jb4dBm01 10 | ! 11 | transceiver qsfp default-mode 4x10G 12 | ! 13 | service routing protocols model ribd 14 | ! 15 | hostname pdx-rtr-eos-03 16 | ! 17 | spanning-tree mode mstp 18 | ! 19 | vrf instance MGMT 20 | ! 21 | management api http-commands 22 | no shutdown 23 | ! 24 | vrf MGMT 25 | no shutdown 26 | ! 27 | interface Ethernet1 28 | description connection to pdx-rtr-eos-04 29 | no switchport 30 | ip address 10.0.34.3/24 31 | ip ospf network point-to-point 32 | ip ospf area 0.0.0.0 33 | ! 34 | interface Ethernet2 35 | description connection to pdx-rtr-eos-02 36 | no switchport 37 | ip address 10.0.23.3/24 38 | ip ospf network point-to-point 39 | ip ospf area 0.0.0.0 40 | ! 41 | interface Ethernet3 42 | ! 43 | interface Ethernet4 44 | ! 45 | interface Ethernet5 46 | ! 47 | interface Ethernet6 48 | ! 49 | interface Ethernet7 50 | ! 51 | interface Ethernet8 52 | ! 53 | interface Management1 54 | vrf MGMT 55 | ip address 192.168.10.123/24 56 | ! 57 | ip routing 58 | no ip routing vrf MGMT 59 | ! 60 | ip route vrf MGMT 0.0.0.0/0 192.168.10.1 61 | ! 62 | router ospf 1 63 | router-id 10.0.0.3 64 | max-lsa 12000 65 | ! 66 | end 67 | -------------------------------------------------------------------------------- /snapshots/configs/pdx-rtr-eos-04.txt: -------------------------------------------------------------------------------- 1 | ! Command: show running-config 2 | ! device: pdx-rtr-eos-04 (vEOS-lab, EOS-4.27.0F) 3 | ! 4 | ! boot system flash:/vEOS-lab.swi 5 | ! 6 | no aaa root 7 | ! 8 | username admin privilege 15 role network-admin secret sha512 $6$SFUWSzuwUCe6.uBi$9HWSTY3bOaQd2LAsMoqc3BGPmjCKWjDerVSH9dh3nJpeUgExs1rBzPn1dvE9/L0TKJxRRmWsjyel2fUnxyoEf0 9 | username suzie privilege 15 role network-admin secret sha512 $6$6zV.qw9F48hskMAL$DW01gDlj1MQbpeBfcLvN.wTVt.j1TTuDd1f7c91n6k5TDx41v5QNsdb7GAO9JyTZy4h1r3yA70Gfe8Jb4dBm01 10 | ! 11 | transceiver qsfp default-mode 4x10G 12 | ! 13 | service routing protocols model ribd 14 | ! 15 | hostname pdx-rtr-eos-04 16 | ! 17 | spanning-tree mode mstp 18 | ! 19 | vrf instance MGMT 20 | ! 21 | management api http-commands 22 | no shutdown 23 | ! 24 | vrf MGMT 25 | no shutdown 26 | ! 27 | interface Ethernet1 28 | description connection to pdx-rtr-eos-03 29 | no switchport 30 | ip address 10.0.34.4/24 31 | ip ospf network point-to-point 32 | ip ospf area 0.0.0.0 33 | ! 34 | interface Ethernet2 35 | no switchport 36 | ip address 192.168.4.1/24 37 | ip ospf area 0.0.0.0 38 | ! 39 | interface Ethernet3 40 | ! 41 | interface Ethernet4 42 | ! 43 | interface Ethernet5 44 | ! 45 | interface Ethernet6 46 | ! 47 | interface Ethernet7 48 | ! 49 | interface Ethernet8 50 | ! 51 | interface Loopback1 52 | ip address 10.0.0.4/32 53 | ip ospf area 0.0.0.0 54 | ! 55 | interface Management1 56 | vrf MGMT 57 | ip address 192.168.10.124/24 58 | ! 59 | ip routing 60 | no ip routing vrf MGMT 61 | ! 62 | ip route vrf MGMT 0.0.0.0/0 192.168.10.1 63 | ! 64 | router bgp 65004 65 | router-id 10.0.0.4 66 | timers bgp 10 30 67 | neighbor 10.0.0.1 remote-as 65001 68 | neighbor 10.0.0.1 update-source Loopback1 69 | neighbor 10.0.0.1 ebgp-multihop 3 70 | ! 71 | router ospf 1 72 | router-id 10.0.0.4 73 | passive-interface Ethernet2 74 | passive-interface Loopback1 75 | max-lsa 12000 76 | ! 77 | end 78 | -------------------------------------------------------------------------------- /suzieq-cfg.yml: -------------------------------------------------------------------------------- 1 | data-directory: /tmp/suz/parquet-out 2 | temp-directory: /tmp/suzieq 3 | logging-level: WARNING 4 | 5 | analyzer: 6 | # By default, the timezone is set to the local timezone. Uncomment 7 | # this line if you want the analyzer (CLI/GUI/REST) to display the time 8 | # in a different timezone than the local time. The timestamp stored in 9 | # the database is always in UTC. 10 | timezone: America/Los_Angeles 11 | -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """Script used to test the network with batfish""" 4 | 5 | from pybatfish.client.commands import * 6 | from pybatfish.question import load_questions 7 | from pybatfish.client.asserts import ( 8 | assert_no_duplicate_router_ids, 9 | assert_no_incompatible_bgp_sessions, 10 | assert_no_incompatible_ospf_sessions, 11 | assert_no_unestablished_bgp_sessions, 12 | assert_no_undefined_references, 13 | ) 14 | from rich.console import Console 15 | 16 | 17 | console = Console(color_system="truecolor") 18 | 19 | 20 | def test_duplicate_rtr_ids(snap): 21 | """Testing for duplicate router IDs""" 22 | console.print( 23 | ":white_exclamation_mark: [bold yellow]Testing for duplicate router IDs[/bold yellow] :white_exclamation_mark:" 24 | ) 25 | assert_no_duplicate_router_ids( 26 | snapshot=snap, 27 | protocols={"ospf", "bgp"}, 28 | ) 29 | console.print( 30 | ":green_heart: [bold green]No duplicate router IDs found[/bold green] :green_heart:" 31 | ) 32 | 33 | 34 | def test_bgp_compatibility(snap): 35 | """Testing for incompatible BGP sessions""" 36 | console.print( 37 | ":white_exclamation_mark: [bold yellow]Testing for incompatible BGP sessions[/bold yellow] :white_exclamation_mark:" 38 | ) 39 | assert_no_incompatible_bgp_sessions( 40 | snapshot=snap, 41 | ) 42 | console.print( 43 | ":green_heart: [bold green]All BGP sessions compatible![/bold green] :green_heart:" 44 | ) 45 | 46 | 47 | def test_ospf_compatibility(snap): 48 | """Testing for incompatible OSPF sessions""" 49 | console.print( 50 | ":white_exclamation_mark: [bold yellow]Testing for incompatible OSPF sessions[/bold yellow] :white_exclamation_mark:" 51 | ) 52 | assert_no_incompatible_ospf_sessions( 53 | snapshot=snap, 54 | ) 55 | console.print( 56 | ":green_heart: [bold green]All OSPF sessions compatible![/bold green] :green_heart:" 57 | ) 58 | 59 | 60 | def test_bgp_unestablished(snap): 61 | """Testing for BGP sessions that are not established""" 62 | console.print( 63 | ":white_exclamation_mark: [bold yellow]Testing for unestablished BGP sessions[/bold yellow] :white_exclamation_mark:" 64 | ) 65 | assert_no_unestablished_bgp_sessions( 66 | snapshot=snap, 67 | ) 68 | console.print( 69 | ":green_heart: [bold green]All BGP sessions are established![/bold green] :green_heart:" 70 | ) 71 | 72 | 73 | def test_undefined_references(snap): 74 | """Testing for any undefined references""" 75 | console.print( 76 | ":white_exclamation_mark: [bold yellow]Testing for undefined references[/bold yellow] :white_exclamation_mark:" 77 | ) 78 | assert_no_undefined_references( 79 | snapshot=snap, 80 | ) 81 | console.print( 82 | ":green_heart: [bold green]No undefined refences found![/bold green] :green_heart:" 83 | ) 84 | 85 | 86 | def main(): 87 | """init all the things""" 88 | NETWORK_NAME = "PDX_NET" 89 | SNAPSHOT_NAME = "snapshot00" 90 | SNAPSHOT_DIR = "./snapshots" 91 | bf_session.host = "192.168.10.193" 92 | bf_set_network(NETWORK_NAME) 93 | init_snap = bf_init_snapshot(SNAPSHOT_DIR, name=SNAPSHOT_NAME, overwrite=True) 94 | load_questions() 95 | test_duplicate_rtr_ids(init_snap) 96 | test_bgp_compatibility(init_snap) 97 | test_ospf_compatibility(init_snap) 98 | test_bgp_unestablished(init_snap) 99 | test_undefined_references(init_snap) 100 | 101 | 102 | if __name__ == "__main__": 103 | main() 104 | -------------------------------------------------------------------------------- /test_suzieq.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """Script used to interact with Suzieq Poller""" 4 | 5 | import sys 6 | import pandas as pd 7 | from suzieq.sqobjects import get_sqobject 8 | from rich.console import Console 9 | 10 | 11 | console = Console(color_system="truecolor") 12 | 13 | # OSPF Testing 14 | ospf_tbl = get_sqobject("ospf") 15 | ospf_df = pd.DataFrame(ospf_tbl().aver()) 16 | ospf_fail = 0 17 | for index, row in ospf_df.iterrows(): 18 | if row["assert"] != "pass": 19 | console.print( 20 | f":triangular_flag_on_post: OSPF, {row['hostname']} {row['ifname']} {row['assertReason']} :triangular_flag_on_post:" 21 | ) 22 | ospf_fail += 1 23 | 24 | # BGP testing 25 | bgp_tbl = get_sqobject("bgp") 26 | bgp_df = pd.DataFrame(bgp_tbl().get()) 27 | bgp_fail = 0 28 | for index, row in bgp_df.iterrows(): 29 | if row["state"] != "Established": 30 | console.print( 31 | f":triangular_flag_on_post: {row['hostname']}(AS {row['asn']}) to {row['peer']}(AS {row['peerAsn']}) is in {row['state']} state :triangular_flag_on_post:" 32 | ) 33 | bgp_fail += 1 34 | 35 | if bgp_fail == 0: 36 | console.print( 37 | ":white_heavy_check_mark: All BGP checks passed :white_heavy_check_mark:" 38 | ) 39 | if ospf_fail == 0: 40 | console.print( 41 | ":white_heavy_check_mark: All OSPF checks passed :white_heavy_check_mark:" 42 | ) 43 | if bgp_fail or ospf_fail != 0: 44 | sys.exit(1) 45 | -------------------------------------------------------------------------------- /tools.py: -------------------------------------------------------------------------------- 1 | """Tools script that holds a variety of functions""" 2 | 3 | import os 4 | 5 | 6 | def nornir_set_creds(norn, username="admin", password=None): 7 | """ 8 | Handler so credentials are not stored in cleartext. 9 | Thank you Kirk! 10 | """ 11 | if not username: 12 | username = os.environ.get("NORNIR_USER") 13 | if not password: 14 | password = os.environ.get("MY_SECRET") 15 | 16 | for host_obj in norn.inventory.hosts.values(): 17 | host_obj.username = username 18 | host_obj.password = password 19 | --------------------------------------------------------------------------------