├── tests ├── __init__.py ├── unit │ ├── __init__.py │ ├── test_utils.py │ └── test_assertions.py ├── inventory ├── test.yml ├── requirements.txt └── e2e │ ├── test_tutorial1_output.yml │ ├── test_tutorials.yml │ ├── test_tutorial3_output.yml │ ├── test_tutorial4_output.yml │ ├── test_tutorial2_output.yml │ └── test_tutorial5_output.yml ├── module_utils ├── __init__.py └── bf_util.py ├── tasks └── main.yml ├── vars └── main.yml ├── action_plugins ├── bf_assert.py ├── bf_extract_facts.py ├── bf_init_snapshot.py ├── bf_set_snapshot.py ├── bf_validate_facts.py ├── bf_upload_diagnostics.py └── bf_action_plugin_common.py ├── defaults └── main.yml ├── handlers └── main.yml ├── tutorials ├── requirements.txt ├── inventory ├── networks │ └── example │ │ ├── example-network.png │ │ ├── hosts │ │ ├── host1.json │ │ └── host2.json │ │ ├── iptables │ │ ├── host1.iptables │ │ └── host2.iptables │ │ └── configs │ │ ├── as1core1.cfg │ │ ├── as3core1.cfg │ │ ├── as2core2.cfg │ │ ├── as2core1.cfg │ │ ├── as2dist1.cfg │ │ ├── as2dist2.cfg │ │ ├── as2dept1.cfg │ │ ├── as3border2.cfg │ │ ├── as3border1.cfg │ │ ├── as1border1.cfg │ │ ├── as2border2.cfg │ │ ├── as1border2.cfg │ │ └── as2border1.cfg ├── playbooks │ ├── batfish_setup.yml │ ├── batfish_docker_stop.yml │ ├── data │ │ ├── validation │ │ │ ├── as1border1.yml │ │ │ ├── as1core1.yml │ │ │ ├── as3border2.yml │ │ │ └── as3border1.yml │ │ └── bf_facts │ │ │ ├── host1.yml │ │ │ └── host2.yml │ ├── tutorial2_validate_facts.yml │ ├── batfish_docker_start.yml │ ├── bf_upload_diagnostics.yml │ ├── batfish_docker_setup.yml │ ├── tutorial1_extract_facts.yml │ ├── tutorial3_validate_forwarding.yml │ ├── tutorial5_validate_bgp_sessions.yml │ ├── tutorial4_validate_acls.yml │ └── batfish_pybatfish_setup.yml └── README.md ├── requirements.txt ├── docs ├── Makefile ├── README.md ├── bf_set_snapshot.rst ├── bf_validate_facts.rst ├── assertion.j2 ├── common.py ├── bf_session.rst ├── bf_assert.rst ├── bf_upload_diagnostics.rst ├── bf_extract_facts.rst ├── bf_init_snapshot.rst └── assertions2rst.py ├── meta └── main.yml ├── .buildkite ├── setup_ansible.sh ├── run_python_unit_tests.sh ├── run_ansible_tutorial_assertions.sh ├── setup_python.sh └── pipeline_precommit.sh ├── CONTRIBUTING.md ├── README.md └── library ├── bf_set_snapshot.py ├── bf_validate_facts.py ├── bf_session.py ├── bf_extract_facts.py ├── bf_init_snapshot.py ├── bf_upload_diagnostics.py └── bf_assert.py /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /module_utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/inventory: -------------------------------------------------------------------------------- 1 | localhost 2 | 3 | -------------------------------------------------------------------------------- /tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # tasks file for ansible -------------------------------------------------------------------------------- /vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # vars file for ansible -------------------------------------------------------------------------------- /action_plugins/bf_assert.py: -------------------------------------------------------------------------------- 1 | bf_action_plugin_common.py -------------------------------------------------------------------------------- /defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # defaults file for ansible -------------------------------------------------------------------------------- /handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # handlers file for ansible -------------------------------------------------------------------------------- /action_plugins/bf_extract_facts.py: -------------------------------------------------------------------------------- 1 | bf_action_plugin_common.py -------------------------------------------------------------------------------- /action_plugins/bf_init_snapshot.py: -------------------------------------------------------------------------------- 1 | bf_action_plugin_common.py -------------------------------------------------------------------------------- /action_plugins/bf_set_snapshot.py: -------------------------------------------------------------------------------- 1 | bf_action_plugin_common.py -------------------------------------------------------------------------------- /action_plugins/bf_validate_facts.py: -------------------------------------------------------------------------------- 1 | bf_action_plugin_common.py -------------------------------------------------------------------------------- /tutorials/requirements.txt: -------------------------------------------------------------------------------- 1 | PyYAML<4.3,>=3.10 2 | docker 3 | -------------------------------------------------------------------------------- /action_plugins/bf_upload_diagnostics.py: -------------------------------------------------------------------------------- 1 | bf_action_plugin_common.py -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | PyYAML<4.3,>=3.10 2 | funcsigs;python_version<"3.3" 3 | -------------------------------------------------------------------------------- /tests/test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: localhost 3 | remote_user: root 4 | roles: 5 | - ansible -------------------------------------------------------------------------------- /tests/requirements.txt: -------------------------------------------------------------------------------- 1 | pytest 2 | mock;python_version<"3.0" 3 | PyYAML 4 | -e git+https://github.com/batfish/pybatfish#egg=pybatfish 5 | -------------------------------------------------------------------------------- /tutorials/inventory: -------------------------------------------------------------------------------- 1 | [all] 2 | www.example.com 3 | [localhost] 4 | ansible_connection=local 5 | localhost ansible_python_interpreter=python -------------------------------------------------------------------------------- /tutorials/networks/example/example-network.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/batfish/ansible/HEAD/tutorials/networks/example/example-network.png -------------------------------------------------------------------------------- /tutorials/playbooks/batfish_setup.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - import_playbook: batfish_pybatfish_setup.yml 3 | - import_playbook: batfish_docker_setup.yml 4 | 5 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # main target to generate all docs 2 | docs: assertions_doc modules_doc 3 | 4 | assertions_doc: 5 | python assertions2rst.py 6 | 7 | modules_doc: 8 | python modules2rst.py 9 | -------------------------------------------------------------------------------- /tutorials/networks/example/hosts/host1.json: -------------------------------------------------------------------------------- 1 | { 2 | "hostname" : "host1", 3 | "iptablesFile" : "iptables/host1.iptables", 4 | "hostInterfaces" : { 5 | "eth0" : { 6 | "name": "eth0", 7 | "prefix" : "2.128.0.101/24", 8 | "gateway": "2.128.0.1" 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /tutorials/networks/example/hosts/host2.json: -------------------------------------------------------------------------------- 1 | { 2 | "hostname" : "host2", 3 | "iptablesFile" : "iptables/host2.iptables", 4 | "hostInterfaces" : { 5 | "eth0" : { 6 | "name": "eth0", 7 | "prefix" : "2.128.1.101/24", 8 | "gateway" : "2.128.1.1" 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /meta/main.yml: -------------------------------------------------------------------------------- 1 | galaxy_info: 2 | author: Batfish 3 | description: Ansible modules for Batfish (www.batfish.org) 4 | role_name: base 5 | 6 | license: Apache 7 | 8 | min_ansible_version: 2.7 9 | 10 | 11 | galaxy_tags: 12 | - batfish 13 | - networking 14 | - validation 15 | 16 | dependencies: [] 17 | -------------------------------------------------------------------------------- /.buildkite/setup_ansible.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Setup Ansible and our Batfish Ansible role 3 | set -euo pipefail 4 | 5 | pip install "ansible==2.9.9" 6 | 7 | # Install our Ansible role from the current branch source 8 | ROLES_DIR=$HOME/.ansible/roles/ 9 | mkdir -p ${ROLES_DIR} 10 | ln -s $(pwd) ${ROLES_DIR}batfish.base 11 | -------------------------------------------------------------------------------- /tutorials/playbooks/batfish_docker_stop.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Stop the Batfish Docker container, if it is running 3 | docker_container: 4 | name: batfish 5 | image: batfish/allinone 6 | state: stopped 7 | register: docker_stop_status 8 | 9 | - name: Print error message 10 | debug: msg="Something went wrong" 11 | when: docker_stop_status.failed|bool == true 12 | -------------------------------------------------------------------------------- /tutorials/playbooks/data/validation/as1border1.yml: -------------------------------------------------------------------------------- 1 | nodes: 2 | as1border1: 3 | BGP: 4 | Neighbors: 5 | 1.10.1.1: 6 | Peer_Group: as1 7 | 10.12.11.2: 8 | Peer_Group: as3 9 | 3.2.2.2: 10 | Peer_Group: bad-ebgp 11 | 5.6.7.8: 12 | Peer_Group: xanadu 13 | DNS: 14 | DNS_Servers: [1.1.1.1] 15 | Domain_Name: lab.local 16 | version: batfish_v0 17 | -------------------------------------------------------------------------------- /.buildkite/run_python_unit_tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euxo pipefail 3 | 4 | if [[ "$1" == "" ]]; then 5 | echo "This script requires one argument specifying which Python version to use (e.g. 2.7)" 6 | exit 1 7 | fi 8 | 9 | BUILDKITE_DIR="$(dirname "${BASH_SOURCE[0]}")" 10 | source ${BUILDKITE_DIR}/setup_python.sh $1 11 | 12 | pip install -r tests/requirements.txt 13 | 14 | # Run the unit tests 15 | pytest tests/unit/ 16 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Ansible modules in batfish.base 2 | 3 | * [bf_assert](bf_assert.rst) 4 | * [bf_extract_facts](bf_extract_facts.rst) 5 | * [bf_init_snapshot](bf_init_snapshot.rst) 6 | * [bf_session](bf_session.rst) 7 | * [bf_set_snapshot](bf_set_snapshot.rst) 8 | * [bf_upload_diagnostics](bf_upload_diagnostics.rst) 9 | * [bf_validate_facts](bf_validate_facts.rst) 10 | 11 | # Additional resources 12 | * [Tutorials](../tutorials) 13 | * [Packaging your network snapshot](https://pybatfish.readthedocs.io/en/latest/notebooks/interacting.html#Packaging-snapshot-data) 14 | -------------------------------------------------------------------------------- /tests/unit/test_utils.py: -------------------------------------------------------------------------------- 1 | from pandas import DataFrame 2 | from pybatfish.datamodel.answer import TableAnswer 3 | from pybatfish.question.question import QuestionBase 4 | 5 | 6 | class MockTableAnswer(TableAnswer): 7 | def __init__(self, frame_to_use=DataFrame()): 8 | self._frame = frame_to_use 9 | 10 | def frame(self): 11 | return self._frame 12 | 13 | 14 | class MockQuestion(QuestionBase): 15 | def __init__(self, answer=None): 16 | self._answer = answer if answer is not None else MockTableAnswer() 17 | 18 | def answer(self, *args, **kwargs): 19 | return self._answer 20 | -------------------------------------------------------------------------------- /tests/e2e/test_tutorial1_output.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Tutorial 1 assertions 3 | hosts: all 4 | gather_facts: no 5 | roles: 6 | - batfish.base 7 | 8 | tasks: 9 | - name: Test 10 | block: 11 | - set_fact: 12 | validate_summary: "Got facts for nodes: '.*', wrote facts to directory: data/bf_facts" 13 | 14 | - name: Check get fact summary indicates facts were retrieved and saved 15 | assert: 16 | that: 17 | - bf_facts.summary == validate_summary 18 | fail_msg: "'{{ bf_facts.summary }}' is not equal to '{{ validate_summary }}'" 19 | delegate_to: localhost 20 | run_once: true 21 | -------------------------------------------------------------------------------- /tests/e2e/test_tutorials.yml: -------------------------------------------------------------------------------- 1 | - import_playbook: ../../tutorials/playbooks/tutorial1_extract_facts.yml 2 | - import_playbook: test_tutorial1_output.yml 3 | 4 | - import_playbook: ../../tutorials/playbooks/tutorial2_validate_facts.yml 5 | - import_playbook: test_tutorial2_output.yml 6 | 7 | - import_playbook: ../../tutorials/playbooks/tutorial3_validate_forwarding.yml 8 | - import_playbook: test_tutorial3_output.yml 9 | 10 | - import_playbook: ../../tutorials/playbooks/tutorial4_validate_acls.yml 11 | - import_playbook: test_tutorial4_output.yml 12 | 13 | - import_playbook: ../../tutorials/playbooks/tutorial5_validate_bgp_sessions.yml 14 | - import_playbook: test_tutorial5_output.yml 15 | -------------------------------------------------------------------------------- /.buildkite/run_ansible_tutorial_assertions.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euxo pipefail 3 | 4 | if [[ "$1" == "" ]]; then 5 | echo "This script requires one argument specifying which Python version to use (e.g. 2.7)" 6 | exit 1 7 | fi 8 | 9 | BUILDKITE_DIR="$(dirname "${BASH_SOURCE[0]}")" 10 | source ${BUILDKITE_DIR}/setup_python.sh $1 11 | source ${BUILDKITE_DIR}/setup_ansible.sh 12 | 13 | pushd tutorials 14 | # Run setup playbook 15 | ansible-playbook -i inventory --extra-vars "batch_mode=True" playbooks/batfish_setup.yml 16 | popd 17 | 18 | # Run the tutorial assertions playbook, validating the output of the tutorials 19 | ansible-playbook -i tutorials/inventory tests/e2e/test_tutorials.yml 20 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Batfish is an open source project licensed under the Apache 2.0 License. 2 | 3 | By submitting a pull request for this project, you are agreeing to license your 4 | contribution under the Apache 2.0 License and to grant a pereptual, world-wide, 5 | non-exclusive, no-charge, royalty-free, irrevocable copyright license to 6 | Batfish and any derivative works. 7 | 8 | You are also representing that you are in a legal position to make this 9 | agreement, on behalf of yourself and your employer as applicable. If the 10 | contribution is itself derivative of other works, you are certifying that the 11 | other projects have licensing requirements compatible with the license of this 12 | project. 13 | -------------------------------------------------------------------------------- /tutorials/networks/example/iptables/host1.iptables: -------------------------------------------------------------------------------- 1 | # Generated by iptables-save v1.4.7 2 | *mangle 3 | :PREROUTING ACCEPT [16586:1618694] 4 | :INPUT ACCEPT [16586:1618694] 5 | :FORWARD ACCEPT [0:0] 6 | :OUTPUT ACCEPT [18957:2978114] 7 | :POSTROUTING ACCEPT [18957:2978114] 8 | COMMIT 9 | 10 | # Generated by iptables-save v1.4.7 11 | *filter 12 | :INPUT DROP [157:11076] 13 | :FORWARD ACCEPT [0:0] 14 | :OUTPUT ACCEPT [114:18840] 15 | -A INPUT -p udp --dport 53 -j ACCEPT 16 | -A INPUT -p tcp --dport 22 -j ACCEPT 17 | COMMIT 18 | 19 | # Generated by iptables-save v1.4.7 on Thu Dec 5 07:40:27 2013 20 | *nat 21 | :PREROUTING ACCEPT [1695:100150] 22 | :POSTROUTING ACCEPT [1626:121319] 23 | :OUTPUT ACCEPT [1626:121319] 24 | COMMIT 25 | -------------------------------------------------------------------------------- /tutorials/networks/example/iptables/host2.iptables: -------------------------------------------------------------------------------- 1 | # Generated by iptables-save v1.4.7 2 | *mangle 3 | :PREROUTING ACCEPT [16586:1618694] 4 | :INPUT ACCEPT [16586:1618694] 5 | :FORWARD ACCEPT [0:0] 6 | :OUTPUT ACCEPT [18957:2978114] 7 | :POSTROUTING ACCEPT [18957:2978114] 8 | COMMIT 9 | 10 | # Generated by iptables-save v1.4.7 11 | *filter 12 | :INPUT DROP [157:11076] 13 | :FORWARD ACCEPT [0:0] 14 | :OUTPUT ACCEPT [114:18840] 15 | -A INPUT -p tcp --dport 22 -j ACCEPT 16 | -A OUTPUT -d 2.128.0.101 -j DROP 17 | COMMIT 18 | 19 | # Generated by iptables-save v1.4.7 on Thu Dec 5 07:40:27 2013 20 | *nat 21 | :PREROUTING ACCEPT [1695:100150] 22 | :POSTROUTING ACCEPT [1626:121319] 23 | :OUTPUT ACCEPT [1626:121319] 24 | COMMIT 25 | 26 | -------------------------------------------------------------------------------- /.buildkite/setup_python.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Setup python for running Batfish Ansible modules and tutorials 3 | set -euo pipefail 4 | 5 | if [[ "$1" == "" ]]; then 6 | echo "This script requires one argument specifying which Python version to use (e.g. 2.7)" 7 | exit 1 8 | fi 9 | 10 | # Setup conda so we can avoid permission issues setting up Python as non-root user 11 | curl -o conda.sh https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh 12 | bash conda.sh -b -f -p $HOME/miniconda 13 | export PATH="$HOME/miniconda/bin:$PATH" 14 | conda create -y -n conda_env python=$1 15 | source activate conda_env 16 | # Get around having to build regex since there is no linux wheel in PyPI 17 | conda install --yes -c conda-forge "regex<=2019.06.08" 18 | # Install role requirements 19 | pip install -r requirements.txt 20 | -------------------------------------------------------------------------------- /tests/e2e/test_tutorial3_output.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Tutorial 3 assertions 3 | hosts: all 4 | gather_facts: no 5 | roles: 6 | - batfish.base 7 | 8 | tasks: 9 | - name: Test 10 | block: 11 | - set_fact: 12 | assert_msg: "1 of 1 assertions failed" 13 | assert_status: "Fail" 14 | 15 | - name: Check assert message indicates assert failure 16 | assert: 17 | that: 18 | - bf_assert.msg == assert_msg 19 | fail_msg: "'{{ bf_assert.msg }}' is not equal to '{{ assert_msg }}'" 20 | - name: Confirm assert_all_flows_succeed failed, found a flow that failed 21 | assert: 22 | that: 23 | - "'Found a flow that failed' in bf_assert.result[0].details" 24 | fail_msg: "'Found a flow that failed' not in '{{ bf_assert.result[0].details }}'" 25 | delegate_to: localhost 26 | run_once: true 27 | -------------------------------------------------------------------------------- /.buildkite/pipeline_precommit.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | BATFISH_DOCKER_CI_BASE_IMAGE="${BATFISH_DOCKER_CI_BASE_IMAGE:-batfish/ci-base:latest}" 5 | DOCKER_PLUGIN_VERSION="${DOCKER_PLUGIN_VERSION:-v3.3.0}" 6 | PYTHON_TEST_VERSIONS=(3.7) 7 | # TODO: dependencies make only 3.7 supported 8 | #PYTHON_TEST_VERSIONS=(3.7 3.8 3.9) 9 | 10 | cat < localhost" and "run_once -> true" 11 | block: 12 | 13 | - include_tasks: batfish_docker_start.yml 14 | 15 | - name: Setup connection to Batfish service 16 | bf_session: 17 | host: localhost 18 | name: local_batfish 19 | 20 | - name: Initialize the example network 21 | bf_init_snapshot: 22 | network: example_network 23 | snapshot: example_snapshot 24 | snapshot_data: ../networks/example 25 | overwrite: true 26 | 27 | - name: Validate facts gathered by Batfish 28 | bf_validate_facts: 29 | expected_facts: data/validation 30 | register: bf_validate 31 | ignore_errors: true 32 | 33 | - name: Display Batfish validation result details 34 | debug: msg="{{bf_validate}}" 35 | 36 | - include_tasks: batfish_docker_stop.yml 37 | 38 | delegate_to: localhost 39 | run_once: true -------------------------------------------------------------------------------- /tutorials/playbooks/batfish_docker_start.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Get list of running docker containers 3 | shell: docker ps 4 | register: status 5 | 6 | - set_fact: 7 | service: false 8 | 9 | - name: Check if batfish/allinone container is running 10 | set_fact: 11 | service: true 12 | when: status.stdout.find('batfish/allinone') != -1 13 | 14 | #NOTE: need to figure out how to allow user to set path for the local data directory 15 | - name: Start the Batfish Docker container, if it is not running 16 | block: 17 | - name: Start the container 18 | docker_container: 19 | name: batfish 20 | image: batfish/allinone 21 | state: started 22 | ports: 23 | - "9996:9996" 24 | - "9997:9997" 25 | volumes: 26 | - "{{ lookup('env','PWD') }}/data:/data" 27 | register: docker_start_status 28 | 29 | - name: Print error message 30 | debug: msg="Something went wrong" 31 | when: docker_start_status.failed|bool == true 32 | 33 | - name: Print Docker Container status 34 | debug: msg="Image; {{docker_container.Config.Image}} - Status; {{docker_container.State}}" 35 | when: docker_start_status.changed|bool == true 36 | 37 | when: service != true 38 | -------------------------------------------------------------------------------- /tutorials/playbooks/bf_upload_diagnostics.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Upload Snapshot Diagnostics 3 | hosts: localhost 4 | gather_facts: no 5 | roles: 6 | - batfish.base 7 | 8 | vars_prompt: 9 | - name: network 10 | prompt: "What is the name of your network?" 11 | private: no 12 | - name: snapshot 13 | prompt: "What is the name of your snapshot?" 14 | private: no 15 | - name: dry_run 16 | prompt: "Would you like to do a dry run? (skip upload)" 17 | private: no 18 | - name: contact_info 19 | prompt: "What contact info would you like associated with your diagnostics? (optional)" 20 | private: no 21 | 22 | tasks: 23 | - include_tasks: batfish_docker_start.yml 24 | 25 | - name: Setup connection to Batfish service 26 | bf_session: 27 | host: localhost 28 | name: local_batfish 29 | 30 | - name: Collect diagnostics 31 | bf_upload_diagnostics: 32 | network: "{{ network }}" 33 | snapshot: "{{ snapshot }}" 34 | dry_run: "{{ dry_run|bool }}" 35 | contact_info: "{{ contact_info }}" 36 | register: diag_action 37 | 38 | - name: Print diagnostics action summary 39 | debug: 40 | var: diag_action.summary 41 | 42 | - include_tasks: batfish_docker_stop.yml 43 | -------------------------------------------------------------------------------- /tests/e2e/test_tutorial2_output.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Tutorial 2 assertions 3 | hosts: all 4 | gather_facts: no 5 | roles: 6 | - batfish.base 7 | 8 | tasks: 9 | - name: Test 10 | block: 11 | - set_fact: 12 | validate_result: {"as1border1": {"BGP.Neighbors.10.12.11.2.Peer_Group": {"actual": "as2", "expected": "as3"}, "DNS.DNS_Servers": {"actual": [], "expected": ["1.1.1.1"]}}, "as1border2": {"Syslog.Logging_Servers": {"actual": [], "expected": ["1.1.1.1"]}, "Syslog.Logging_Source_Interface": {"actual": null, "expected": "Loopback0"}}} 13 | 14 | - name: Check validate fact summary indicates validation failed 15 | assert: 16 | that: 17 | - "'Validation failed for the following nodes' in bf_validate.msg" 18 | - "'as1border1' in bf_validate.msg" 19 | - "'as1border2' in bf_validate.msg" 20 | fail_msg: "'Validation failed for the following nodes: ['as1border2', 'as1border1']' not in '{{ bf_validate.result }}'" 21 | - name: Check validate fact detailed results indicate actual vs expected fact mismatch 22 | assert: 23 | that: 24 | - bf_validate.result == validate_result 25 | fail_msg: "'{{ bf_validate.result }}' is not equal to '{{ validate_result }}'" 26 | delegate_to: localhost 27 | run_once: true 28 | -------------------------------------------------------------------------------- /tests/e2e/test_tutorial5_output.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Tutorial 5 assertions 3 | hosts: all 4 | gather_facts: no 5 | roles: 6 | - batfish.base 7 | 8 | tasks: 9 | - name: Test 10 | block: 11 | - set_fact: 12 | assert_msg: "2 of 3 assertions failed" 13 | 14 | - name: Confirm 2 of 3 asserts failed 15 | assert: 16 | that: 17 | - bf_assert.msg == assert_msg 18 | fail_msg: "'{{ bf_assert.msg }}' not equal to '{{ assert_msg }}'" 19 | - name: Confirm assert_no_undefined_references failed, found undefined references 20 | assert: 21 | that: 22 | - "'Found undefined reference(s)' in bf_assert.result[0].details" 23 | fail_msg: "'Found undefined reference(s)' not in '{{ bf_assert.result[0].details }}'" 24 | - name: Confirm assert_no_incompatible_bgp_sessions failed, found incompatible BGP session 25 | assert: 26 | that: 27 | - "'Found incompatible BGP session(s)' in bf_assert.result[1].details" 28 | fail_msg: "'Found incompatible BGP session(s)' not in '{{ bf_assert.result[1].details }}'" 29 | - name: Confirm assert_no_unestablished_bgp_sessions passed 30 | assert: 31 | that: 32 | - "'Assertion passed' == bf_assert.result[2].details" 33 | fail_msg: "'Assertion passed' not equal to '{{ bf_assert.result[2].details }}'" 34 | delegate_to: localhost 35 | run_once: true 36 | -------------------------------------------------------------------------------- /tutorials/playbooks/batfish_docker_setup.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Playbook to install latest Batfish docker container on localhost 3 | hosts: localhost 4 | connection: local 5 | gather_facts: no 6 | 7 | tasks: 8 | 9 | - name: Check if docker is installed 10 | shell: "docker --version" 11 | register: docker_installed 12 | 13 | - name: Download latest Batfish Docker image 14 | block: 15 | #Note; If the local image is the latest version, this does NOTHING. So the variable `docker_dwld` is just empty 16 | - name: Pulling Batfish Docker container from Dockerhub 17 | docker_image: 18 | repository: batfish/allinone 19 | name: batfish/allinone 20 | force: yes 21 | state: present 22 | pull: yes 23 | register: docker_dwld 24 | 25 | - name: Print error message 26 | debug: msg="Something went wrong" 27 | when: docker_dwld.failed|bool == true 28 | 29 | - name: Print information about image download 30 | debug: msg="{{docker_dwld.image}}" 31 | when: docker_dwld.failed|bool == false 32 | 33 | when: docker_installed.failed|bool == false and docker_installed.stdout is search("Docker version") 34 | 35 | - name: Warn user about issue with Docker installation 36 | debug: msg="Docker is not running (or installed). Please fix and try again" 37 | when: docker_installed.failed|bool != false 38 | -------------------------------------------------------------------------------- /tutorials/playbooks/tutorial1_extract_facts.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Welcome to the first Batfish Tutorial! 3 | hosts: all 4 | gather_facts: no 5 | roles: 6 | - batfish.base 7 | 8 | tasks: 9 | 10 | - name: Execute Batfish related tasks in a block that is "delegate_to -> localhost" and "run_once -> true" 11 | block: 12 | 13 | - include_tasks: batfish_docker_start.yml 14 | 15 | - name: Setup connection to Batfish service 16 | bf_session: 17 | host: localhost 18 | name: local_batfish 19 | 20 | - name: Initialize the example network 21 | bf_init_snapshot: 22 | network: example_network 23 | snapshot: example_snapshot 24 | snapshot_data: ../networks/example 25 | overwrite: true 26 | 27 | - name: Retrieve Batfish Facts 28 | bf_extract_facts: 29 | output_directory: data/bf_facts 30 | register: bf_facts 31 | 32 | - name: Display select facts gathered by Batfish 33 | block: 34 | 35 | - name: Display Batfish Facts for as1border1 36 | debug: msg="{{bf_facts.result.nodes.as1border1}}" 37 | 38 | - name: Display NTP configuration for all nodes 39 | debug: msg=" {{item.value.NTP}} " 40 | with_dict: "{{bf_facts.result.nodes}}" 41 | loop_control: 42 | label: " {{item.key}}.NTP " 43 | 44 | when: bf_facts.failed|bool == false 45 | 46 | - include_tasks: batfish_docker_stop.yml 47 | 48 | delegate_to: localhost 49 | run_once: true -------------------------------------------------------------------------------- /tutorials/playbooks/tutorial3_validate_forwarding.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Welcome to the third Batfish Tutorial! 3 | hosts: all 4 | gather_facts: no 5 | roles: 6 | - batfish.base 7 | 8 | tasks: 9 | 10 | - name: Execute Batfish related tasks in a block that is "delegate_to -> localhost" and "run_once -> true" 11 | block: 12 | 13 | - include_tasks: batfish_docker_start.yml 14 | 15 | - name: Setup connection to Batfish service 16 | bf_session: 17 | host: localhost 18 | name: local_batfish 19 | 20 | - name: Initialize the example network 21 | bf_init_snapshot: 22 | network: example_network 23 | snapshot: example_snapshot 24 | snapshot_data: ../networks/example 25 | overwrite: true 26 | 27 | - name: Validate that a service is reachable from outside the network 28 | bf_assert: 29 | assertions: 30 | - type: assert_all_flows_succeed 31 | name: Confirm web server is reachable for HTTPS traffic received on GigEth0/0 on as1border1 32 | parameters: 33 | startLocation: '@enter(as1border1[GigabitEthernet0/0])' 34 | headers: 35 | dstIps: '2.128.0.101' 36 | ipProtocols: 'tcp' 37 | dstPorts: '443' 38 | register: bf_assert 39 | ignore_errors: true 40 | 41 | - name: Display Batfish validation result details 42 | debug: msg="{{bf_assert}}" 43 | 44 | - include_tasks: batfish_docker_stop.yml 45 | 46 | delegate_to: localhost 47 | run_once: true 48 | -------------------------------------------------------------------------------- /tutorials/playbooks/tutorial5_validate_bgp_sessions.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Welcome to the fifth Batfish Tutorial! 3 | hosts: all 4 | gather_facts: no 5 | roles: 6 | - batfish.base 7 | 8 | tasks: 9 | 10 | - name: Execute Batfish related tasks in a block that is "delegate_to -> localhost" and "run_once -> true" 11 | block: 12 | 13 | - include_tasks: batfish_docker_start.yml 14 | 15 | - name: Setup connection to Batfish service 16 | bf_session: 17 | host: localhost 18 | name: local_batfish 19 | 20 | - name: Initialize the example network 21 | bf_init_snapshot: 22 | network: example_network 23 | snapshot: example_snapshot 24 | snapshot_data: ../networks/example 25 | overwrite: true 26 | 27 | - name: Validate the configuration of network devices 28 | bf_assert: 29 | assertions: 30 | - type: assert_no_undefined_references 31 | name: Confirm that there are NO undefined references on any network device 32 | 33 | - type: assert_no_incompatible_bgp_sessions 34 | name: Confirm that all BGP peers are properly configured 35 | 36 | - type: assert_no_unestablished_bgp_sessions 37 | name: Confirm that all compatible BGP peers establish sessions 38 | register: bf_assert 39 | ignore_errors: true 40 | 41 | - name: Display Batfish validation result details 42 | debug: msg="{{bf_assert}}" 43 | 44 | - include_tasks: batfish_docker_stop.yml 45 | 46 | delegate_to: localhost 47 | run_once: true 48 | -------------------------------------------------------------------------------- /tutorials/playbooks/tutorial4_validate_acls.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Welcome to the fourth Batfish Tutorial! 3 | hosts: all 4 | gather_facts: no 5 | roles: 6 | - batfish.base 7 | 8 | tasks: 9 | 10 | - name: Execute Batfish related tasks in a block that is "delegate_to -> localhost" and "run_once -> true" 11 | block: 12 | 13 | - include_tasks: batfish_docker_start.yml 14 | 15 | - name: Setup connection to Batfish service 16 | bf_session: 17 | host: localhost 18 | name: local_batfish 19 | 20 | - name: Initialize the example network 21 | bf_init_snapshot: 22 | network: example_network 23 | snapshot: example_snapshot 24 | snapshot_data: ../networks/example 25 | overwrite: true 26 | 27 | - name: Validate the behavior of a packet filter (ACL/Firewall rule) 28 | bf_assert: 29 | assertions: 30 | - type: assert_filter_has_no_unreachable_lines 31 | name: Confirm that the filter has any unreachable lines 32 | parameters: 33 | filters: 'as2border2["101"]' 34 | 35 | - type: assert_filter_denies 36 | name: Confirm that the filter drops SSH traffic 37 | parameters: 38 | filters: 'as2border2["101"]' 39 | headers: 40 | applications: 'ssh' 41 | register: bf_assert 42 | ignore_errors: true 43 | 44 | - name: Display Batfish validation result details 45 | debug: msg="{{bf_assert}}" 46 | 47 | - include_tasks: batfish_docker_stop.yml 48 | 49 | delegate_to: localhost 50 | run_once: true 51 | -------------------------------------------------------------------------------- /tutorials/networks/example/configs/as1core1.cfg: -------------------------------------------------------------------------------- 1 | 2 | ! 3 | version 15.2 4 | service timestamps debug datetime msec 5 | service timestamps log datetime msec 6 | ! 7 | hostname as1core1 8 | ! 9 | boot-start-marker 10 | boot-end-marker 11 | ! 12 | ! 13 | logging host 1.1.1.1 14 | logging host 2.2.2.2 15 | ! 16 | ! 17 | no aaa new-model 18 | no ip icmp rate-limit unreachable 19 | ip cef 20 | ! 21 | ! 22 | ! 23 | ! 24 | ! 25 | ! 26 | no ip domain lookup 27 | ip domain name lab.local 28 | no ipv6 cef 29 | ! 30 | ! 31 | multilink bundle-name authenticated 32 | ! 33 | ! 34 | ! 35 | ! 36 | ! 37 | ! 38 | ! 39 | ! 40 | ! 41 | ip tcp synwait-time 5 42 | ! 43 | ! 44 | ! 45 | ! 46 | ! 47 | ! 48 | ! 49 | ! 50 | ! 51 | ! 52 | ! 53 | ! 54 | interface Loopback0 55 | ip address 1.10.1.1 255.255.255.255 56 | ! 57 | interface Ethernet0/0 58 | no ip address 59 | shutdown 60 | duplex auto 61 | ! 62 | interface GigabitEthernet0/0 63 | ip address 1.0.2.2 255.255.255.0 64 | media-type gbic 65 | speed 1000 66 | duplex full 67 | negotiation auto 68 | ! 69 | interface GigabitEthernet1/0 70 | ip address 1.0.1.2 255.255.255.0 71 | negotiation auto 72 | ! 73 | router ospf 1 74 | router-id 1.10.1.1 75 | network 1.0.0.0 0.255.255.255 area 1 76 | ! 77 | router bgp 1 78 | bgp router-id 1.10.1.1 79 | bgp log-neighbor-changes 80 | neighbor as1 peer-group 81 | neighbor as1 remote-as 1 82 | neighbor 1.1.1.1 peer-group as1 83 | neighbor 1.1.1.1 update-source Loopback0 84 | neighbor 1.2.2.2 peer-group as1 85 | neighbor 1.2.2.2 update-source Loopback0 86 | ! 87 | address-family ipv4 88 | bgp dampening 89 | bgp additional-paths select all 90 | bgp additional-paths send receive 91 | neighbor as1 send-community 92 | neighbor as1 route-reflector-client 93 | neighbor as1 advertise additional-paths all 94 | neighbor 1.1.1.1 activate 95 | neighbor 1.2.2.2 activate 96 | maximum-paths eibgp 5 97 | exit-address-family 98 | ! 99 | ip forward-protocol nd 100 | ! 101 | ! 102 | no ip http server 103 | no ip http secure-server 104 | ! 105 | ! 106 | ! 107 | ! 108 | control-plane 109 | ! 110 | ! 111 | line con 0 112 | exec-timeout 0 0 113 | privilege level 15 114 | logging synchronous 115 | stopbits 1 116 | line aux 0 117 | exec-timeout 0 0 118 | privilege level 15 119 | logging synchronous 120 | stopbits 1 121 | line vty 0 4 122 | login 123 | ! 124 | ! 125 | end 126 | -------------------------------------------------------------------------------- /tutorials/playbooks/data/bf_facts/host1.yml: -------------------------------------------------------------------------------- 1 | nodes: 2 | host1: 3 | AS_Path_Access_Lists: [] 4 | Authentication_Key_Chains: [] 5 | Configuration_Format: HOST 6 | DNS: 7 | DNS_Servers: [] 8 | DNS_Source_Interface: null 9 | Default_Cross_Zone_Action: PERMIT 10 | Default_Inbound_Action: PERMIT 11 | Domain_Name: null 12 | Hostname: host1 13 | IP6_Access_Lists: [] 14 | IP_Access_Lists: 15 | - filter::FORWARD 16 | - filter::INPUT 17 | - filter::OUTPUT 18 | - mangle::FORWARD 19 | - mangle::INPUT 20 | - mangle::OUTPUT 21 | - mangle::POSTROUTING 22 | - mangle::PREROUTING 23 | - nat::OUTPUT 24 | - nat::POSTROUTING 25 | - nat::PREROUTING 26 | IPsec: 27 | IKE_Phase1_Keys: [] 28 | IKE_Phase1_Policies: [] 29 | IKE_Phase1_Proposals: [] 30 | IPsec_Peer_Configs: [] 31 | IPsec_Phase2_Policies: [] 32 | IPsec_Phase2_Proposals: [] 33 | Interfaces: 34 | eth0: 35 | Access_VLAN: null 36 | Active: true 37 | All_Prefixes: 38 | - 2.128.0.101/24 39 | Allowed_VLANs: '' 40 | Auto_State_VLAN: true 41 | Bandwidth: 1000000000.0 42 | Blacklisted: false 43 | Channel_Group: null 44 | Channel_Group_Members: [] 45 | DHCP_Relay_Addresses: [] 46 | Declared_Names: 47 | - eth0 48 | Description: null 49 | Encapsulation_VLAN: null 50 | HSRP_Groups: [] 51 | HSRP_Version: null 52 | Incoming_Filter_Name: filter::INPUT 53 | MLAG_ID: null 54 | MTU: 1500 55 | Native_VLAN: null 56 | Outgoing_Filter_Name: filter::OUTPUT 57 | PBR_Policy_Name: null 58 | Primary_Address: 2.128.0.101/24 59 | Primary_Network: 2.128.0.0/24 60 | Proxy_ARP: false 61 | Rip_Enabled: false 62 | Rip_Passive: false 63 | Spanning_Tree_Portfast: false 64 | Speed: null 65 | Switchport: false 66 | Switchport_Mode: NONE 67 | Switchport_Trunk_Encapsulation: DOT1Q 68 | VRF: default 69 | VRRP_Groups: [] 70 | Zone_Name: null 71 | NTP: 72 | NTP_Servers: [] 73 | NTP_Source_Interface: null 74 | PBR_Policies: [] 75 | Route6_Filter_Lists: [] 76 | Route_Filter_Lists: [] 77 | Routing_Policies: [] 78 | SNMP: 79 | SNMP_Source_Interface: null 80 | SNMP_Trap_Servers: [] 81 | Syslog: 82 | Logging_Servers: [] 83 | Logging_Source_Interface: null 84 | TACACS: 85 | TACACS_Servers: [] 86 | TACACS_Source_Interface: null 87 | VRFs: 88 | - default 89 | Zones: [] 90 | version: batfish_v0 91 | -------------------------------------------------------------------------------- /tutorials/playbooks/data/bf_facts/host2.yml: -------------------------------------------------------------------------------- 1 | nodes: 2 | host2: 3 | AS_Path_Access_Lists: [] 4 | Authentication_Key_Chains: [] 5 | Configuration_Format: HOST 6 | DNS: 7 | DNS_Servers: [] 8 | DNS_Source_Interface: null 9 | Default_Cross_Zone_Action: PERMIT 10 | Default_Inbound_Action: PERMIT 11 | Domain_Name: null 12 | Hostname: host2 13 | IP6_Access_Lists: [] 14 | IP_Access_Lists: 15 | - filter::FORWARD 16 | - filter::INPUT 17 | - filter::OUTPUT 18 | - mangle::FORWARD 19 | - mangle::INPUT 20 | - mangle::OUTPUT 21 | - mangle::POSTROUTING 22 | - mangle::PREROUTING 23 | - nat::OUTPUT 24 | - nat::POSTROUTING 25 | - nat::PREROUTING 26 | IPsec: 27 | IKE_Phase1_Keys: [] 28 | IKE_Phase1_Policies: [] 29 | IKE_Phase1_Proposals: [] 30 | IPsec_Peer_Configs: [] 31 | IPsec_Phase2_Policies: [] 32 | IPsec_Phase2_Proposals: [] 33 | Interfaces: 34 | eth0: 35 | Access_VLAN: null 36 | Active: true 37 | All_Prefixes: 38 | - 2.128.1.101/24 39 | Allowed_VLANs: '' 40 | Auto_State_VLAN: true 41 | Bandwidth: 1000000000.0 42 | Blacklisted: false 43 | Channel_Group: null 44 | Channel_Group_Members: [] 45 | DHCP_Relay_Addresses: [] 46 | Declared_Names: 47 | - eth0 48 | Description: null 49 | Encapsulation_VLAN: null 50 | HSRP_Groups: [] 51 | HSRP_Version: null 52 | Incoming_Filter_Name: filter::INPUT 53 | MLAG_ID: null 54 | MTU: 1500 55 | Native_VLAN: null 56 | Outgoing_Filter_Name: filter::OUTPUT 57 | PBR_Policy_Name: null 58 | Primary_Address: 2.128.1.101/24 59 | Primary_Network: 2.128.1.0/24 60 | Proxy_ARP: false 61 | Rip_Enabled: false 62 | Rip_Passive: false 63 | Spanning_Tree_Portfast: false 64 | Speed: null 65 | Switchport: false 66 | Switchport_Mode: NONE 67 | Switchport_Trunk_Encapsulation: DOT1Q 68 | VRF: default 69 | VRRP_Groups: [] 70 | Zone_Name: null 71 | NTP: 72 | NTP_Servers: [] 73 | NTP_Source_Interface: null 74 | PBR_Policies: [] 75 | Route6_Filter_Lists: [] 76 | Route_Filter_Lists: [] 77 | Routing_Policies: [] 78 | SNMP: 79 | SNMP_Source_Interface: null 80 | SNMP_Trap_Servers: [] 81 | Syslog: 82 | Logging_Servers: [] 83 | Logging_Source_Interface: null 84 | TACACS: 85 | TACACS_Servers: [] 86 | TACACS_Source_Interface: null 87 | VRFs: 88 | - default 89 | Zones: [] 90 | version: batfish_v0 91 | -------------------------------------------------------------------------------- /tutorials/networks/example/configs/as3core1.cfg: -------------------------------------------------------------------------------- 1 | 2 | ! 3 | version 15.2 4 | service timestamps debug datetime msec 5 | service timestamps log datetime msec 6 | ! 7 | hostname as3core1 8 | ! 9 | boot-start-marker 10 | boot-end-marker 11 | ! 12 | ! 13 | logging host 1.1.1.1 14 | logging host 2.2.2.2 15 | ! 16 | ! 17 | no aaa new-model 18 | no ip icmp rate-limit unreachable 19 | ip cef 20 | ! 21 | ! 22 | ! 23 | ! 24 | ! 25 | ! 26 | no ip domain lookup 27 | ip domain name lab.local 28 | no ipv6 cef 29 | ! 30 | ! 31 | multilink bundle-name authenticated 32 | ! 33 | ! 34 | ! 35 | ! 36 | ! 37 | ! 38 | ! 39 | ! 40 | ! 41 | ip tcp synwait-time 5 42 | ! 43 | ! 44 | ! 45 | ! 46 | ! 47 | ! 48 | ! 49 | ! 50 | ! 51 | ! 52 | ! 53 | ! 54 | interface Loopback0 55 | ip address 3.10.1.1 255.255.255.255 56 | ! 57 | interface Ethernet0/0 58 | no ip address 59 | shutdown 60 | duplex auto 61 | ! 62 | interface GigabitEthernet0/0 63 | ip address 3.0.2.2 255.255.255.0 64 | media-type gbic 65 | speed 1000 66 | duplex full 67 | negotiation auto 68 | ! 69 | interface GigabitEthernet1/0 70 | ip address 3.0.1.2 255.255.255.0 71 | negotiation auto 72 | ! 73 | interface GigabitEthernet2/0 74 | ip address 90.90.90.1 255.255.255.0 75 | negotiation auto 76 | ! 77 | interface GigabitEthernet3/0 78 | ip address 90.90.90.2 255.255.255.0 79 | negotiation auto 80 | ! 81 | router ospf 1 82 | network 3.0.0.0 0.255.255.255 area 1 83 | ! 84 | router bgp 3 85 | bgp router-id 3.10.1.1 86 | bgp log-neighbor-changes 87 | neighbor as3 peer-group 88 | neighbor as3 remote-as 3 89 | neighbor 3.1.1.1 peer-group as3 90 | neighbor 3.1.1.1 update-source Loopback0 91 | neighbor 3.2.2.2 peer-group as3 92 | neighbor 3.2.2.2 update-source Loopback0 93 | ! 94 | address-family ipv4 95 | bgp dampening 96 | bgp additional-paths select all 97 | bgp additional-paths send receive 98 | network 3.0.1.0 mask 255.255.255.0 99 | network 3.0.2.0 mask 255.255.255.0 100 | neighbor as3 send-community 101 | neighbor as3 route-reflector-client 102 | neighbor as3 advertise additional-paths all 103 | neighbor 3.1.1.1 activate 104 | neighbor 3.2.2.2 activate 105 | maximum-paths eibgp 5 106 | exit-address-family 107 | ! 108 | ip forward-protocol nd 109 | ! 110 | ! 111 | no ip http server 112 | no ip http secure-server 113 | ! 114 | ! 115 | ! 116 | ! 117 | control-plane 118 | ! 119 | ! 120 | line con 0 121 | exec-timeout 0 0 122 | privilege level 15 123 | logging synchronous 124 | stopbits 1 125 | line aux 0 126 | exec-timeout 0 0 127 | privilege level 15 128 | logging synchronous 129 | stopbits 1 130 | line vty 0 4 131 | login 132 | ! 133 | ! 134 | end 135 | -------------------------------------------------------------------------------- /docs/bf_set_snapshot.rst: -------------------------------------------------------------------------------- 1 | .. _bf_set_snapshot: 2 | 3 | bf_set_snapshot 4 | +++++++++++++++ 5 | Set the current Batfish snapshot 6 | 7 | .. contents:: 8 | :local: 9 | :depth: 2 10 | 11 | 12 | Synopsis 13 | -------- 14 | 15 | 16 | * Set the current Batfish network and snapshot. 17 | 18 | 19 | 20 | Requirements 21 | ------------ 22 | The following software packages must be installed on hosts that execute this module: 23 | 24 | * pybatfish 25 | 26 | 27 | 28 | .. _module-specific-options-label: 29 | 30 | Module-specific Options 31 | ----------------------- 32 | The following options may be specified for this module: 33 | 34 | .. raw:: html 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 74 | 75 | 76 |
parametertyperequireddefaultcomments
network
stryes 52 |
Name of the network to set as the active network.
53 |
session
dictnoValue in the bf_session fact. 62 |
Batfish session object required to connect to the Batfish service.
63 |
snapshot
stryes 72 |
Name of the snapshot to set as the active snapshot. This snapshot must already exist.
73 |
77 |
78 | 79 | .. _bf_set_snapshot-examples-label: 80 | 81 | Examples 82 | -------- 83 | 84 | :: 85 | 86 | 87 | # Set the current snapshot and network names 88 | - bf_set_snapshot 89 | network: datacenter_sea 90 | snapshot: 2019-01-01 91 | 92 | 93 | 94 | Return Values 95 | ------------- 96 | 97 | .. raw:: html 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 114 | 115 | 116 | 117 | 118 |
namedescriptionreturnedtype
summary 112 |
Summary of action(s) performed.
113 |
alwaysstr
119 |
120 |
121 | 122 | 123 | 124 | 125 | 126 | Status 127 | ~~~~~~ 128 | 129 | This module is flagged as **preview** which means that it is not guaranteed to have a backwards compatible interface. 130 | 131 | 132 | -------------------------------------------------------------------------------- /module_utils/bf_util.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # Copyright 2019 The Batfish Open Source Project 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | from pybatfish.client._diagnostics import ( 17 | check_if_all_passed, check_if_any_failed, get_snapshot_parse_status 18 | ) 19 | from pybatfish.client.session import Session 20 | 21 | _UPLOAD_DIAGNOSTICS_DOC_URL = 'https://github.com/batfish/ansible/blob/master/docs/bf_upload_diagnostics.rst' 22 | 23 | NODE_SPECIFIER_INSTRUCTIONS_URL = 'https://github.com/batfish/batfish/blob/master/questions/Parameters.md#node-specifier' 24 | 25 | 26 | def create_session(session_type='bf', **params): 27 | """Create session with the supplied params.""" 28 | return Session.get(type_=session_type, **params) 29 | 30 | 31 | def set_snapshot(session, network, snapshot): 32 | """Set the network and snapshot for the specified session.""" 33 | session.set_network(network) 34 | session.set_snapshot(snapshot) 35 | 36 | 37 | def get_snapshot_init_warning(session): 38 | """Return warning message if the snapshot initialization had issues (parse warnings, errors).""" 39 | statuses = get_snapshot_parse_status(session) 40 | if check_if_any_failed(statuses): 41 | return 'Your snapshot was initialized but Batfish failed to parse one or more input files. You can proceed but some analysis may be incorrect. You can help the Batfish developers improve support for your network by running the bf_upload_diagnostics module: {}'.format( 42 | _UPLOAD_DIAGNOSTICS_DOC_URL) 43 | if not check_if_all_passed(statuses): 44 | return 'Your snapshot was successfully initialized but Batfish failed to fully recognized some lines in one or more input files. Some unrecognized configuration lines are not uncommon for new networks, and it is often fine to proceed with further analysis. You can help the Batfish developers improve support for your network by running the bf_upload_diagnostics module: {}'.format( 45 | _UPLOAD_DIAGNOSTICS_DOC_URL) 46 | return None 47 | 48 | 49 | def get_node_count(facts): 50 | """Return the number of nodes in the supplied facts.""" 51 | nodes, version = _unencapsulate_facts(facts) 52 | return len(nodes) 53 | 54 | 55 | def _unencapsulate_facts(facts, check_version=False): 56 | """Extract node facts and version from final fact format.""" 57 | assert 'nodes' in facts, 'No nodes present in parsed facts file(s)' 58 | return facts['nodes'], facts.get('version') 59 | -------------------------------------------------------------------------------- /tutorials/networks/example/configs/as2core2.cfg: -------------------------------------------------------------------------------- 1 | 2 | ! 3 | version 15.2 4 | service timestamps debug datetime msec 5 | service timestamps log datetime msec 6 | ! 7 | hostname as2core2 8 | ! 9 | boot-start-marker 10 | boot-end-marker 11 | ! 12 | ! 13 | logging host 1.1.1.1 14 | logging host 2.2.2.2 15 | ! 16 | ! 17 | no aaa new-model 18 | no ip icmp rate-limit unreachable 19 | ip cef 20 | ! 21 | ! 22 | ! 23 | ! 24 | ! 25 | ! 26 | no ip domain lookup 27 | ip domain name lab.local 28 | no ipv6 cef 29 | ! 30 | ! 31 | multilink bundle-name authenticated 32 | ! 33 | ! 34 | ! 35 | ! 36 | ! 37 | ! 38 | ! 39 | ! 40 | ! 41 | ip tcp synwait-time 5 42 | ! 43 | ! 44 | ! 45 | ! 46 | ! 47 | ! 48 | ! 49 | ! 50 | ! 51 | ! 52 | ! 53 | ! 54 | interface Loopback0 55 | ip address 2.1.2.2 255.255.255.255 56 | ! 57 | interface Ethernet0/0 58 | no ip address 59 | shutdown 60 | duplex auto 61 | ! 62 | interface GigabitEthernet0/0 63 | mtu 1800 64 | ip address 2.12.22.2 255.255.255.0 65 | media-type gbic 66 | speed 1000 67 | duplex full 68 | negotiation auto 69 | ! 70 | interface GigabitEthernet1/0 71 | mtu 1600 72 | ip address 2.12.12.2 255.255.255.0 73 | negotiation auto 74 | ! 75 | interface GigabitEthernet2/0 76 | mtu 1700 77 | ip address 2.23.22.2 255.255.255.0 78 | negotiation auto 79 | ! 80 | interface GigabitEthernet3/0 81 | ip address 2.23.21.2 255.255.255.0 82 | negotiation auto 83 | ! 84 | router ospf 1 85 | router-id 2.1.2.2 86 | network 2.0.0.0 0.255.255.255 area 1 87 | ! 88 | router bgp 2 89 | bgp router-id 2.1.2.2 90 | bgp log-neighbor-changes 91 | neighbor as2 peer-group 92 | neighbor as2 remote-as 2 93 | neighbor as3 peer-group 94 | neighbor as3 remote-as 3 95 | neighbor 2.1.1.1 peer-group as2 96 | neighbor 2.1.1.1 update-source Loopback0 97 | neighbor 2.1.1.2 peer-group as2 98 | neighbor 2.1.1.2 update-source Loopback0 99 | neighbor 2.1.3.1 peer-group as2 100 | neighbor 2.1.3.1 update-source Loopback0 101 | neighbor 2.1.3.2 peer-group as2 102 | neighbor 2.1.3.2 update-source Loopback0 103 | ! 104 | address-family ipv4 105 | bgp dampening 106 | bgp additional-paths select all 107 | bgp additional-paths send receive 108 | neighbor as2 send-community 109 | neighbor as2 route-reflector-client 110 | neighbor as2 advertise additional-paths all 111 | neighbor as3 route-map filter-bogons in 112 | neighbor 2.1.1.1 activate 113 | neighbor 2.1.1.2 activate 114 | neighbor 2.1.3.1 activate 115 | neighbor 2.1.3.2 activate 116 | maximum-paths eibgp 5 117 | exit-address-family 118 | ! 119 | ip forward-protocol nd 120 | ! 121 | ! 122 | no ip http server 123 | no ip http secure-server 124 | ! 125 | ! 126 | ! 127 | ! 128 | control-plane 129 | ! 130 | ! 131 | line con 0 132 | exec-timeout 0 0 133 | privilege level 15 134 | logging synchronous 135 | stopbits 1 136 | line aux 0 137 | exec-timeout 0 0 138 | privilege level 15 139 | logging synchronous 140 | stopbits 1 141 | line vty 0 4 142 | login 143 | ! 144 | ! 145 | end 146 | -------------------------------------------------------------------------------- /tutorials/networks/example/configs/as2core1.cfg: -------------------------------------------------------------------------------- 1 | 2 | ! 3 | version 15.2 4 | service timestamps debug datetime msec 5 | service timestamps log datetime msec 6 | ! 7 | hostname as2core1 8 | ! 9 | boot-start-marker 10 | boot-end-marker 11 | ! 12 | ! 13 | logging host 1.1.1.1 14 | logging host 2.1.2.2 15 | ! 16 | ! 17 | no aaa new-model 18 | no ip icmp rate-limit unreachable 19 | ip cef 20 | ! 21 | ! 22 | ! 23 | ! 24 | ! 25 | ! 26 | no ip domain lookup 27 | ip domain name lab.local 28 | no ipv6 cef 29 | ! 30 | ! 31 | multilink bundle-name authenticated 32 | ! 33 | ! 34 | ! 35 | ! 36 | ! 37 | ! 38 | ! 39 | ! 40 | ! 41 | ip tcp synwait-time 5 42 | ! 43 | ! 44 | ! 45 | ! 46 | ! 47 | ! 48 | ! 49 | ! 50 | ! 51 | ! 52 | ! 53 | ! 54 | interface Loopback0 55 | ip address 2.1.2.1 255.255.255.255 56 | ! 57 | interface Ethernet0/0 58 | no ip address 59 | shutdown 60 | duplex auto 61 | ! 62 | interface GigabitEthernet0/0 63 | ip address 2.12.11.2 255.255.255.0 64 | media-type gbic 65 | speed 1000 66 | duplex full 67 | negotiation auto 68 | ! 69 | interface GigabitEthernet1/0 70 | ip address 2.12.21.2 255.255.255.0 71 | negotiation auto 72 | ! 73 | interface GigabitEthernet2/0 74 | ip address 2.23.11.2 255.255.255.0 75 | ip access-group blocktelnet in 76 | negotiation auto 77 | ! 78 | interface GigabitEthernet3/0 79 | ip address 2.23.12.2 255.255.255.0 80 | ip access-group blocktelnet in 81 | negotiation auto 82 | ! 83 | router ospf 1 84 | router-id 2.1.2.1 85 | network 2.0.0.0 0.255.255.255 area 1 86 | ! 87 | router bgp 2 88 | bgp router-id 2.1.2.1 89 | bgp log-neighbor-changes 90 | neighbor as2 peer-group 91 | neighbor as2 remote-as 2 92 | neighbor 2.1.1.1 peer-group as2 93 | neighbor 2.1.1.1 update-source Loopback0 94 | neighbor 2.1.1.2 peer-group as2 95 | neighbor 2.1.1.2 update-source Loopback0 96 | neighbor 2.1.3.1 peer-group as2 97 | neighbor 2.1.3.1 update-source Loopback0 98 | neighbor 2.1.3.2 peer-group as2 99 | neighbor 2.1.3.2 update-source Loopback0 100 | ! 101 | address-family ipv4 102 | bgp dampening 103 | bgp additional-paths select all 104 | bgp additional-paths send receive 105 | neighbor as2 send-community 106 | neighbor as2 route-reflector-client 107 | neighbor as2 advertise additional-paths all 108 | neighbor 2.1.1.1 activate 109 | neighbor 2.1.1.2 activate 110 | neighbor 2.1.3.1 activate 111 | neighbor 2.1.3.2 activate 112 | maximum-paths eibgp 5 113 | exit-address-family 114 | ! 115 | ip forward-protocol nd 116 | ! 117 | ! 118 | no ip http server 119 | no ip http secure-server 120 | ! 121 | ip access-list extended blocktelnet 122 | deny tcp any any eq telnet 123 | permit ip any any 124 | ! 125 | ! 126 | ! 127 | ! 128 | control-plane 129 | ! 130 | ! 131 | line con 0 132 | exec-timeout 0 0 133 | privilege level 15 134 | logging synchronous 135 | stopbits 1 136 | line aux 0 137 | exec-timeout 0 0 138 | privilege level 15 139 | logging synchronous 140 | stopbits 1 141 | line vty 0 4 142 | login 143 | ! 144 | ! 145 | end 146 | -------------------------------------------------------------------------------- /tutorials/playbooks/batfish_pybatfish_setup.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Playbook to install Pybatfish on localhost 3 | hosts: localhost 4 | connection: local 5 | gather_facts: no 6 | 7 | tasks: 8 | 9 | - name: Check if in Python VENV 10 | shell: "python -c 'import sys; print(\"1\" if (hasattr(sys, \"real_prefix\") or (hasattr(sys,\"base_prefix\") and sys.base_prefix != sys.prefix)) else \"0\")'" 11 | register: python_venv 12 | 13 | - name: Not in Python VENV, ask user to proceed or not 14 | block: 15 | - name: Python VENV not-detected 16 | debug: msg="Python virtual environment NOT detected" 17 | 18 | - name: Prompt if user wants to continue 19 | pause: 20 | prompt: "Do you want to continue? (y/n)" 21 | echo: yes 22 | register: venv_prompt 23 | when: batch_mode is not defined 24 | 25 | when: python_venv.stdout[0] == "0" 26 | 27 | 28 | - debug: 29 | var: venv_prompt 30 | 31 | - set_fact: 32 | venv_prompt: "y" 33 | when: batch_mode is defined or python_venv.stdout[0] == "1" or venv_prompt.user_input == "Y" or venv_prompt.user_input == "y" 34 | 35 | 36 | - name: Proceeding with pybatfish installation based on user input 37 | block: 38 | - name: Retrieve Python version 39 | shell: "python --version" 40 | register: python_installed 41 | 42 | - name: Warn if Python 3 is not running 43 | debug: msg="Python version 2 detected. We recommend that you use Python 3" 44 | when: python_installed.stdout is search('Python 2') 45 | 46 | - name: Upgrade pip 47 | shell: "python -m pip install --upgrade pip" 48 | register: pip_upgrade 49 | 50 | - name: Pip upgrade error 51 | debug: msg="Something went wrong" 52 | when: pip_upgrade.failed|bool == true 53 | 54 | - name: Print information about pip upgrade 55 | debug: msg="{{pip_upgrade.stdout}}" 56 | when: pip_upgrade.changed|bool == true 57 | 58 | - name: Download and install Pybatfish (Python SDK for Batfish) 59 | shell: "python -m pip install --upgrade git+https://github.com/batfish/pybatfish.git" 60 | register: pybatfish_install 61 | 62 | - name: Pybatfish installation error 63 | debug: msg="Something went wrong" 64 | when: pybatfish_install.failed|bool == true 65 | 66 | - name: Print information about Pybatfish install 67 | debug: msg="{{pybatfish_install.stdout}}" 68 | when: pybatfish_install.changed|bool == true 69 | 70 | - name: Install tutorial requirements 71 | shell: "python -m pip install -r ../requirements.txt" 72 | register: requirement_install 73 | 74 | - name: Requirement installation error 75 | debug: msg="Something went wrong" 76 | when: requirement_install.failed|bool == true 77 | 78 | - name: Print information about requirement install 79 | debug: msg="{{requirement_install.stdout}}" 80 | when: requirement_install.changed|bool == true 81 | 82 | when: venv_prompt == "Y" or venv_prompt == "y" 83 | -------------------------------------------------------------------------------- /tutorials/README.md: -------------------------------------------------------------------------------- 1 | # Tutorials for the batfish.base role 2 | 3 | This repository contains example playbooks that show how to use Batfish in conjunction with Ansible. 4 | 5 | - [tutorial1_extract_facts.yml](playbooks/tutorial1_extract_facts.yml): Shows how to retrieve facts about network devices 6 | 7 | - [tutorial2_validate_facts.yml](playbooks/tutorial2_validate_facts.yml): Shows how to validate facts about network devices 8 | 9 | - [tutorial3_validate_forwarding.yml](playbooks/tutorial3_validate_forwarding.yml): Shows how to validate the routing and forwarding behavior of the network 10 | 11 | - [tutorial4_validate_acls.yml](playbooks/tutorial4_validate_acls.yml): Shows how to validate the behavior of a packet filter(ACL/Firewall rule) 12 | 13 | - [tutorial5_validate_bgp_sessions.yml](playbooks/tutorial5_validate_bgp_sessions.yml): Shows how to validate configuration attributes to find mis-configured BGP sessions and undefined references 14 | 15 | - [bf_upload_diagnostics.yml](playbooks/bf_upload_diagnostics.yml): Shows how to upload diagnostic information about your snapshot in the event of issues (e.g. if Batfish fails to fully recognized some lines in your input files) 16 | 17 | ## Setup 18 | 19 | - Ensure that Docker is installed and running on your machine. 20 | 21 | - Install the latest version of the `batfish.base` role from Ansible Galaxy. 22 | 23 | `ansible-galaxy install --force batfish.base` 24 | - If you encounter any issues related to SSL certificates use the `-c` option. 25 | 26 | - Clone or download this repository to your local machine, and run the setup playblook. 27 | 28 | - Setup Batfish on your local machine 29 | 30 | `cd tutorials` 31 | 32 | `ansible-playbook -i inventory playbooks/batfish_setup.yml` 33 | 34 | This playbook will download and install the latest Batfish docker container, Pybatfish SDK and other Python requirements. It has some rudimentary error checking built into it: 35 | - Aborts if Docker is not running. 36 | - Warns if it does not detect a Python virtual environment and ask you to confirm that you want to proceed with the installation. 37 | 38 | 39 | ## Running a tutorial 40 | 41 | We highly recommend that you run the tutorials in a Python 3 virtual environment. Details on how to set one up can be found [here](https://docs.python.org/3/library/venv.html). 42 | 43 | From the `tutorials` directory, run specific tutorials via 44 | 45 | `ansible-playbook -i inventory playbooks/tutorial1_extract_facts.yml` 46 | 47 | Each playbook is a standalone tutorial, it does not depend on other tutorials having been run first. So feel free to execute them in whatever order you think is best. 48 | 49 | Batfish is designed to provide complete network analysis, which is why all tasks calling Batfish modules should set `delegate_to: localhost` and `run_once: true`. The tutorials are set up that way, so you can incorporate non-Batfish tasks that need to run across other hosts in your inventory. 50 | 51 | 52 | ## Documentation 53 | 54 | - [Documentation for the Ansible modules](../docs/README.md) 55 | 56 | - [Packaging your own network snapshots](https://pybatfish.readthedocs.io/en/latest/notebooks/interacting.html#Packaging-snapshot-data) 57 | 58 | -------------------------------------------------------------------------------- /tutorials/networks/example/configs/as2dist1.cfg: -------------------------------------------------------------------------------- 1 | 2 | ! 3 | version 15.2 4 | service timestamps debug datetime msec 5 | service timestamps log datetime msec 6 | ! 7 | hostname as2dist1 8 | ! 9 | boot-start-marker 10 | boot-end-marker 11 | ! 12 | ! 13 | ! 14 | no aaa new-model 15 | no ip icmp rate-limit unreachable 16 | ip cef 17 | ! 18 | ! 19 | ! 20 | ! 21 | ! 22 | ! 23 | no ip domain lookup 24 | ip domain name lab.local 25 | no ipv6 cef 26 | ! 27 | ! 28 | multilink bundle-name authenticated 29 | ! 30 | ! 31 | ! 32 | ! 33 | ! 34 | ! 35 | ! 36 | ! 37 | ! 38 | ip tcp synwait-time 5 39 | ! 40 | ! 41 | ! 42 | ! 43 | ! 44 | ! 45 | ! 46 | ! 47 | ! 48 | ! 49 | ! 50 | ! 51 | interface Loopback0 52 | ip address 2.1.3.1 255.255.255.255 53 | ! 54 | interface Ethernet0/0 55 | no ip address 56 | shutdown 57 | duplex auto 58 | ! 59 | interface GigabitEthernet0/0 60 | ip address 2.23.11.3 255.255.255.0 61 | media-type gbic 62 | speed 1000 63 | duplex full 64 | negotiation auto 65 | ! 66 | interface GigabitEthernet1/0 67 | ip address 2.23.21.3 255.255.255.0 68 | negotiation auto 69 | ! 70 | interface GigabitEthernet2/0 71 | ip address 2.34.101.3 255.255.255.0 72 | negotiation auto 73 | ! 74 | router ospf 1 75 | router-id 2.1.3.1 76 | redistribute connected subnets 77 | network 2.1.0.0 0.0.255.255 area 1 78 | network 2.23.0.0 0.0.255.255 area 1 79 | ! 80 | router bgp 2 81 | bgp router-id 2.1.3.1 82 | bgp log-neighbor-changes 83 | neighbor as2 peer-group 84 | neighbor as2 remote-as 2 85 | neighbor dept peer-group 86 | neighbor dept remote-as 65001 87 | neighbor 2.1.2.1 peer-group as2 88 | neighbor 2.1.2.1 update-source Loopback0 89 | neighbor 2.1.2.2 peer-group as2 90 | neighbor 2.1.2.2 update-source Loopback0 91 | neighbor 2.34.101.4 peer-group dept 92 | ! 93 | address-family ipv4 94 | bgp dampening 95 | bgp additional-paths select all 96 | bgp additional-paths send receive 97 | neighbor as2 send-community 98 | neighbor as2 advertise additional-paths all 99 | neighbor dept send-community 100 | neighbor dept route-map dept_to_as2dist in 101 | neighbor dept route-map as2dist_to_dept out 102 | neighbor 2.1.2.1 activate 103 | neighbor 2.1.2.2 activate 104 | neighbor 2.34.101.4 activate 105 | maximum-paths eibgp 5 106 | exit-address-family 107 | ! 108 | ip forward-protocol nd 109 | ! 110 | ip bgp-community new-format 111 | ip community-list expanded dept_community permit _65001: 112 | ! 113 | no ip http server 114 | no ip http secure-server 115 | ! 116 | access-list 102 permit ip host 2.128.0.0 host 255.255.0.0 117 | access-list 105 permit ip host 1.0.1.0 host 255.255.255.0 118 | access-list 105 permit ip host 1.0.2.0 host 255.255.255.0 119 | access-list 105 permit ip host 3.0.1.0 host 255.255.255.0 120 | access-list 105 permit ip host 3.0.2.0 host 255.255.255.0 121 | ! 122 | route-map as2dist_to_dept permit 100 123 | match ip address 105 124 | set metric 50 125 | set community 2:65001 additive 126 | ! 127 | route-map dept_to_as2dist permit 100 128 | match community dept_community 129 | set local-preference 350 130 | ! 131 | ! 132 | ! 133 | control-plane 134 | ! 135 | ! 136 | line con 0 137 | exec-timeout 0 0 138 | privilege level 15 139 | logging synchronous 140 | stopbits 1 141 | line aux 0 142 | exec-timeout 0 0 143 | privilege level 15 144 | logging synchronous 145 | stopbits 1 146 | line vty 0 4 147 | login 148 | ! 149 | ! 150 | end 151 | -------------------------------------------------------------------------------- /tutorials/networks/example/configs/as2dist2.cfg: -------------------------------------------------------------------------------- 1 | 2 | ! 3 | version 15.2 4 | service timestamps debug datetime msec 5 | service timestamps log datetime msec 6 | ! 7 | hostname as2dist2 8 | ! 9 | boot-start-marker 10 | boot-end-marker 11 | ! 12 | ! 13 | ! 14 | no aaa new-model 15 | no ip icmp rate-limit unreachable 16 | ip cef 17 | ! 18 | ! 19 | ! 20 | ! 21 | ! 22 | ! 23 | no ip domain lookup 24 | ip domain name lab.local 25 | no ipv6 cef 26 | ! 27 | ! 28 | multilink bundle-name authenticated 29 | ! 30 | ! 31 | ! 32 | ! 33 | ! 34 | ! 35 | ! 36 | ! 37 | ! 38 | ip tcp synwait-time 5 39 | ! 40 | ! 41 | ! 42 | ! 43 | ! 44 | ! 45 | ! 46 | ! 47 | ! 48 | ! 49 | ! 50 | ! 51 | interface Loopback0 52 | ip address 2.1.3.2 255.255.255.255 53 | ! 54 | interface Ethernet0/0 55 | no ip address 56 | shutdown 57 | duplex auto 58 | ! 59 | interface GigabitEthernet0/0 60 | ip address 2.23.22.3 255.255.255.0 61 | media-type gbic 62 | speed 1000 63 | duplex full 64 | negotiation auto 65 | ! 66 | interface GigabitEthernet1/0 67 | ip address 2.23.12.3 255.255.255.0 68 | negotiation auto 69 | ! 70 | interface GigabitEthernet2/0 71 | ip address 2.34.201.3 255.255.255.0 72 | negotiation auto 73 | ! 74 | router ospf 1 75 | router-id 2.1.3.2 76 | redistribute connected subnets 77 | network 2.1.0.0 0.0.255.255 area 1 78 | network 2.23.0.0 0.0.255.255 area 1 79 | ! 80 | router bgp 2 81 | bgp router-id 2.1.3.2 82 | bgp log-neighbor-changes 83 | neighbor as2 peer-group 84 | neighbor as2 remote-as 2 85 | neighbor dept peer-group 86 | neighbor dept remote-as 65001 87 | neighbor 2.1.2.1 peer-group as2 88 | neighbor 2.1.2.1 update-source Loopback0 89 | neighbor 2.1.2.2 peer-group as2 90 | neighbor 2.1.2.2 update-source Loopback0 91 | neighbor 2.34.201.4 peer-group dept 92 | ! 93 | address-family ipv4 94 | bgp dampening 95 | bgp additional-paths select all 96 | bgp additional-paths send receive 97 | neighbor as2 send-community 98 | neighbor as2 advertise additional-paths all 99 | neighbor dept send-community 100 | neighbor dept route-map dept_to_as2dist in 101 | neighbor dept route-map as2dist_to_dept out 102 | neighbor 2.1.2.1 activate 103 | neighbor 2.1.2.2 activate 104 | neighbor 2.34.201.4 activate 105 | maximum-paths eibgp 5 106 | exit-address-family 107 | ! 108 | ip forward-protocol nd 109 | ! 110 | ip bgp-community new-format 111 | ip community-list expanded dept_community permit _65001: 112 | ! 113 | no ip http server 114 | no ip http secure-server 115 | ! 116 | access-list 102 permit ip host 2.128.0.0 host 255.255.0.0 117 | access-list 105 permit ip host 1.0.1.0 host 255.255.255.0 118 | access-list 105 permit ip host 1.0.2.0 host 255.255.255.0 119 | access-list 105 permit ip host 3.0.1.0 host 255.255.255.0 120 | access-list 105 permit ip host 3.0.2.0 host 255.255.255.0 121 | ! 122 | route-map as2dist_to_dept permit 100 123 | match ip address 105 124 | set metric 50 125 | set community 2:65001 additive 126 | ! 127 | route-map dept_to_as2dist permit 100 128 | match community dept_community 129 | set local-preference 350 130 | ! 131 | ! 132 | ! 133 | control-plane 134 | ! 135 | ! 136 | line con 0 137 | exec-timeout 0 0 138 | privilege level 15 139 | logging synchronous 140 | stopbits 1 141 | line aux 0 142 | exec-timeout 0 0 143 | privilege level 15 144 | logging synchronous 145 | stopbits 1 146 | line vty 0 4 147 | login 148 | ! 149 | ! 150 | end 151 | -------------------------------------------------------------------------------- /tutorials/networks/example/configs/as2dept1.cfg: -------------------------------------------------------------------------------- 1 | 2 | ! 3 | version 15.2 4 | service timestamps debug datetime msec 5 | service timestamps log datetime msec 6 | ! 7 | hostname as2dept1 8 | ! 9 | boot-start-marker 10 | boot-end-marker 11 | ! 12 | ! 13 | ! 14 | no aaa new-model 15 | no ip icmp rate-limit unreachable 16 | ip cef 17 | ! 18 | ! 19 | ! 20 | ! 21 | ! 22 | ! 23 | no ip domain lookup 24 | ip domain name lab.local 25 | no ipv6 cef 26 | ! 27 | ! 28 | multilink bundle-name authenticated 29 | ! 30 | ! 31 | ! 32 | ! 33 | ! 34 | ! 35 | ! 36 | ! 37 | ! 38 | ip tcp synwait-time 5 39 | ! 40 | ! 41 | ! 42 | ! 43 | ! 44 | ! 45 | ! 46 | ! 47 | ! 48 | ! 49 | ! 50 | ! 51 | interface Loopback0 52 | ip address 2.1.1.2 255.255.255.255 53 | ! 54 | interface Ethernet0/0 55 | no ip address 56 | shutdown 57 | duplex auto 58 | ! 59 | interface GigabitEthernet0/0 60 | ip address 2.34.101.4 255.255.255.0 61 | media-type gbic 62 | speed 1000 63 | duplex full 64 | negotiation auto 65 | ! 66 | interface GigabitEthernet1/0 67 | ip address 2.34.201.4 255.255.255.0 68 | negotiation auto 69 | ! 70 | interface GigabitEthernet2/0 71 | ip address 2.128.0.1 255.255.255.0 72 | ip access-group RESTRICT_HOST_TRAFFIC_IN in 73 | negotiation auto 74 | ! 75 | interface GigabitEthernet3/0 76 | ip address 2.128.1.1 255.255.255.0 77 | ip access-group RESTRICT_HOST_TRAFFIC_IN in 78 | negotiation auto 79 | ! 80 | router bgp 65001 81 | bgp router-id 2.1.4.1 82 | bgp log-neighbor-changes 83 | neighbor as2 peer-group 84 | neighbor as2 remote-as 2 85 | neighbor 2.34.101.3 peer-group as2 86 | neighbor 2.34.201.3 peer-group as2 87 | ! 88 | address-family ipv4 89 | bgp dampening 90 | bgp additional-paths select all 91 | bgp additional-paths send receive 92 | network 2.128.0.0 mask 255.255.255.0 93 | network 2.128.1.0 mask 255.255.255.0 94 | neighbor as2 send-community 95 | neighbor as2 route-map as2_to_dept in 96 | neighbor as2 route-map dept_to_as2 out 97 | neighbor 2.34.101.3 activate 98 | neighbor 2.34.201.3 activate 99 | maximum-paths eibgp 5 100 | exit-address-family 101 | ! 102 | ip forward-protocol nd 103 | ! 104 | ip bgp-community new-format 105 | ip community-list expanded as2_community permit _2: 106 | ! 107 | no ip http server 108 | no ip http secure-server 109 | ! 110 | ip access-list extended RESTRICT_HOST_TRAFFIC_IN 111 | permit ip 2.128.0.0 0.0.255.255 any 112 | deny ip any any 113 | permit icmp any any 114 | ip access-list extended RESTRICT_HOST_TRAFFIC_OUT 115 | permit ip any 2.128.0.0 0.0.255.255 116 | deny ip 1.128.0.0 0.0.255.255 2.128.0.0 0.0.255.255 117 | deny ip any any 118 | ! 119 | access-list 102 permit ip host 2.128.0.0 host 255.255.255.0 120 | access-list 102 permit ip host 2.128.1.0 host 255.255.255.0 121 | access-list 105 permit ip host 1.0.1.0 host 255.255.255.0 122 | access-list 105 permit ip host 1.0.2.0 host 255.255.255.0 123 | access-list 105 permit ip host 3.0.1.0 host 255.255.255.0 124 | access-list 105 permit ip host 3.0.2.0 host 255.255.255.0 125 | ! 126 | route-map dept_to_as2 permit 100 127 | match ip address 102 128 | set metric 50 129 | set community 65001:2 additive 130 | ! 131 | route-map as2_to_dept permit 100 132 | match community as2_community 133 | set local-preference 350 134 | ! 135 | ! 136 | ! 137 | control-plane 138 | ! 139 | ! 140 | line con 0 141 | exec-timeout 0 0 142 | privilege level 15 143 | logging synchronous 144 | stopbits 1 145 | line aux 0 146 | exec-timeout 0 0 147 | privilege level 15 148 | logging synchronous 149 | stopbits 1 150 | line vty 0 4 151 | login 152 | ! 153 | ! 154 | end 155 | -------------------------------------------------------------------------------- /docs/bf_validate_facts.rst: -------------------------------------------------------------------------------- 1 | .. _bf_validate_facts: 2 | 3 | bf_validate_facts 4 | +++++++++++++++++ 5 | Validates facts for the current Batfish snapshot against the facts in the supplied directory 6 | 7 | .. contents:: 8 | :local: 9 | :depth: 2 10 | 11 | 12 | Synopsis 13 | -------- 14 | 15 | 16 | * Validates facts for the current Batfish snapshot against the facts in the ``expected_facts`` directory 17 | 18 | 19 | 20 | Requirements 21 | ------------ 22 | The following software packages must be installed on hosts that execute this module: 23 | 24 | * pybatfish 25 | 26 | 27 | 28 | .. _module-specific-options-label: 29 | 30 | Module-specific Options 31 | ----------------------- 32 | The following options may be specified for this module: 33 | 34 | .. raw:: html 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 84 | 85 | 86 |
parametertyperequireddefaultcomments
expected_facts
stryes 52 |
Directory to read expected facts from.
53 |
network
strnoValue in the bf_network fact. 62 |
Name of the network to validate facts for.
63 |
session
dictnoValue in the bf_session fact. 72 |
Batfish session object required to connect to the Batfish service.
73 |
snapshot
strnoValue in the bf_snapshot fact. 82 |
Name of the snapshot to validate facts for.
83 |
87 |
88 | 89 | .. _bf_validate_facts-examples-label: 90 | 91 | Examples 92 | -------- 93 | 94 | :: 95 | 96 | 97 | # Validate current snapshot facts against local YAML facts 98 | - bf_validate_facts: 99 | expected_facts: /path/to/local/YAML/files/ 100 | 101 | 102 | 103 | Return Values 104 | ------------- 105 | 106 | .. raw:: html 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 132 | 133 | 134 | 135 | 136 |
namedescriptionreturnedtype
result 121 |
Contains a map of node-name to dictionary of failures for that node.
122 |
when validation does not passdict
summary 130 |
Summary of action(s) performed.
131 |
alwaysstr
137 |
138 |
139 | 140 | 141 | 142 | 143 | 144 | Status 145 | ~~~~~~ 146 | 147 | This module is flagged as **preview** which means that it is not guaranteed to have a backwards compatible interface. 148 | 149 | 150 | -------------------------------------------------------------------------------- /docs/assertion.j2: -------------------------------------------------------------------------------- 1 | .. _@{ assertion_name }@: 2 | 3 | {% set title = assertion_name %} 4 | {% set title_len = title|length %} 5 | @{ title }@ 6 | @{ '-' * title_len }@ 7 | {% if short_description %} 8 | @{ short_description|convert_symbols_to_format }@ 9 | {% endif %} 10 | 11 | 12 | {% if description %} 13 | 14 | {% for desc in description -%} 15 | * @{ desc | convert_symbols_to_format }@ 16 | {% endfor %} 17 | 18 | 19 | {% endif %} 20 | {% if options -%} 21 | 22 | The following parameters may be specified for this assertion: 23 | 24 | .. raw:: html 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | {% for k in option_keys -%} 36 | {% set v = options[k] -%} 37 | {% if not v['suboptions'] %} 38 | 39 | 40 | 41 | 42 | 43 | 44 | 58 | 59 | 60 | 61 | 62 | 63 | 71 | 72 | 73 | 110 | 111 | {% endif %} 112 | 113 | 114 | {% endfor %} 115 | 116 |
parametertyperequireddefaultcomments
@{ k }@
{% if v['version_added'] -%} (added in @{v['version_added']}@){% endif -%}
{% if v['type'] -%}@{ v['type'] }@{% endif -%}{% if v.get('required', False) -%}yes{% else %}no{% endif -%}{% if v.get('default', None) is not none -%}@{ v['default'] | string | html_ify }@{% endif -%} 45 | {% if v.description is string %} 46 |
@{ v.description | replace('\n', '\n ') | html_ify }@
47 | {% else %} 48 | {% for desc in v.description %} 49 |
@{ desc | replace('\n', '\n ') | html_ify }@
50 | {% endfor %} 51 | {% endif %} 52 | {% if 'aliases' in v and v.aliases %} 53 |
aliases: @{ v.aliases|join(', ') }@
54 | {% endif %} 55 | {% else %} 56 | 57 |
@{ k }@
{% if v['version_added'] -%} (added in @{v['version_added']}@){% endif -%}
{% if v['type'] -%}@{ v['type'] }@{% endif -%}{% if v.get('required', False) -%}yes{% else -%}no{% endif -%} 64 | {% for desc in v.description %} 65 |
@{ desc | replace('\n', '\n ') | html_ify }@
66 | {% endfor %} 67 | {% if 'aliases' in v and v.aliases %} 68 |
aliases: @{ v.aliases|join(', ') }@
69 | {% endif %} 70 |
74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | {% for k2 in v['suboptions'] %} 85 | {% set v2 = v['suboptions'] [k2] %} 86 | 87 | 88 | 89 | 90 | 91 | 92 | 104 | 105 | {% endfor %} 106 | 107 |
Dictionary object @{ k }@
parametertyperequireddefaultcomments
@{ k2 }@
{% if v2['version_added'] -%} (added in @{v2['version_added']}@){% endif -%}
{% if v2['type'] -%}@{ v2['type'] }@{% endif -%}{% if v2.get('required', False) -%}yes{% else -%}no{% endif -%}{% if v2.get('default', None) is not none -%}@{ v2['default'] | string | html_ify }@{% endif -%} 93 | {% if v2.description is string %} 94 |
@{ v2.description | replace('\n', '\n ') | html_ify }@
95 | {% else %} 96 | {% for desc in v2.description %} 97 |
@{ desc | replace('\n', '\n ') | html_ify }@
98 | {% endfor %} 99 | {% endif %} 100 | {% if 'aliases' in v and v2.aliases %} 101 |
aliases: @{ v2.aliases|join(', ') }@
102 | {% endif %} 103 |
108 | 109 |
117 |
118 | 119 | {% endif %} 120 | -------------------------------------------------------------------------------- /tutorials/networks/example/configs/as3border2.cfg: -------------------------------------------------------------------------------- 1 | 2 | ! 3 | version 15.2 4 | service timestamps debug datetime msec 5 | service timestamps log datetime msec 6 | ! 7 | hostname as3border2 8 | ! 9 | boot-start-marker 10 | boot-end-marker 11 | ! 12 | ! 13 | ntp server 18.18.18.18 14 | ntp server 23.23.23.23 15 | ! 16 | ! 17 | no aaa new-model 18 | no ip icmp rate-limit unreachable 19 | ip cef 20 | ! 21 | ! 22 | ! 23 | ! 24 | ! 25 | ! 26 | no ip domain lookup 27 | ip domain name lab.local 28 | no ipv6 cef 29 | ! 30 | ! 31 | multilink bundle-name authenticated 32 | ! 33 | ! 34 | ! 35 | ! 36 | ! 37 | ! 38 | ! 39 | ! 40 | ! 41 | ip tcp synwait-time 5 42 | ! 43 | ! 44 | ! 45 | ! 46 | ! 47 | ! 48 | ! 49 | ! 50 | ! 51 | ! 52 | ! 53 | ! 54 | interface Loopback0 55 | ip address 3.2.2.2 255.255.255.255 56 | ! 57 | interface Ethernet0/0 58 | no ip address 59 | shutdown 60 | duplex auto 61 | ! 62 | interface GigabitEthernet0/0 63 | ip address 10.13.22.3 255.255.255.0 64 | media-type gbic 65 | speed 1000 66 | duplex full 67 | negotiation auto 68 | ! 69 | interface GigabitEthernet1/0 70 | ip address 3.0.2.1 255.255.255.0 71 | negotiation auto 72 | ! 73 | router ospf 1 74 | router-id 3.2.2.2 75 | redistribute connected subnets 76 | network 3.0.0.0 0.255.255.255 area 1 77 | ! 78 | router bgp 3 79 | bgp router-id 3.2.2.2 80 | bgp log-neighbor-changes 81 | neighbor as1 peer-group 82 | neighbor as1 remote-as 1 83 | neighbor as2 peer-group 84 | neighbor as2 remote-as 2 85 | neighbor as3 peer-group 86 | neighbor as3 remote-as 3 87 | neighbor 3.10.1.1 peer-group as3 88 | neighbor 3.10.1.1 update-source Loopback0 89 | neighbor 10.13.22.1 peer-group as1 90 | ! 91 | address-family ipv4 92 | bgp dampening 93 | bgp additional-paths select all 94 | bgp additional-paths send receive 95 | network 3.0.1.0 mask 255.255.255.0 96 | network 3.0.2.0 mask 255.255.255.0 97 | neighbor as1 send-community 98 | neighbor as1 route-map as1_to_as3 in 99 | neighbor as1 route-map as3_to_as1 out 100 | neighbor as2 send-community 101 | neighbor as2 route-map as2_to_as3 in 102 | neighbor as2 route-map as3_to_as2 out 103 | neighbor as3 send-community 104 | neighbor as3 advertise additional-paths all 105 | neighbor 3.10.1.1 activate 106 | neighbor 10.13.22.1 activate 107 | maximum-paths eibgp 5 108 | exit-address-family 109 | ! 110 | ip forward-protocol nd 111 | ! 112 | ip bgp-community new-format 113 | ip community-list expanded as1_community permit _1: 114 | ip community-list expanded as2_community permit _2: 115 | ip community-list expanded as3_community permit _3: 116 | ! 117 | no ip http server 118 | no ip http secure-server 119 | ! 120 | ! 121 | ip prefix-list inbound_route_filter seq 5 deny 3.0.0.0/8 le 32 122 | ip prefix-list inbound_route_filter seq 10 permit 0.0.0.0/0 le 32 123 | access-list 101 permit ip host 1.0.1.0 host 255.255.255.0 124 | access-list 101 permit ip host 1.0.2.0 host 255.255.255.0 125 | access-list 102 permit ip host 2.0.0.0 host 255.0.0.0 126 | access-list 102 permit ip host 2.128.0.0 host 255.255.0.0 127 | access-list 103 permit ip host 3.0.1.0 host 255.255.255.0 128 | access-list 103 permit ip host 3.0.2.0 host 255.255.255.0 129 | ! 130 | route-map as3_to_as1 permit 2 131 | match ip address 102 132 | set metric 50 133 | set community 3:1 additive 134 | ! 135 | route-map as3_to_as1 permit 3 136 | match ip address 103 137 | set metric 50 138 | set community 3:1 additive 139 | ! 140 | route-map as1_to_as3 permit 100 141 | match community as1_community 142 | set local-preference 350 143 | ! 144 | route-map as3_to_as2 permit 1 145 | match ip address 101 146 | set metric 50 147 | set community 3:2 additive 148 | ! 149 | route-map as3_to_as2 permit 3 150 | match ip address 103 151 | set metric 50 152 | set community 3:2 additive 153 | ! 154 | route-map as2_to_as3 permit 100 155 | match community as2_community 156 | set local-preference 350 157 | ! 158 | ! 159 | ! 160 | control-plane 161 | ! 162 | ! 163 | line con 0 164 | exec-timeout 0 0 165 | privilege level 15 166 | logging synchronous 167 | stopbits 1 168 | line aux 0 169 | exec-timeout 0 0 170 | privilege level 15 171 | logging synchronous 172 | stopbits 1 173 | line vty 0 4 174 | login 175 | ! 176 | ! 177 | end 178 | -------------------------------------------------------------------------------- /tutorials/networks/example/configs/as3border1.cfg: -------------------------------------------------------------------------------- 1 | 2 | ! 3 | version 15.2 4 | service timestamps debug datetime msec 5 | service timestamps log datetime msec 6 | ! 7 | hostname as3border1 8 | ! 9 | boot-start-marker 10 | boot-end-marker 11 | ! 12 | ! 13 | ntp server 18.18.18.18 14 | ntp server 23.23.23.23 15 | ! 16 | ! 17 | no aaa new-model 18 | no ip icmp rate-limit unreachable 19 | ip cef 20 | ! 21 | ! 22 | ! 23 | ! 24 | ! 25 | ! 26 | no ip domain lookup 27 | ip domain name lab.local 28 | no ipv6 cef 29 | ! 30 | ! 31 | multilink bundle-name authenticated 32 | ! 33 | ! 34 | ! 35 | ! 36 | ! 37 | ! 38 | ! 39 | ! 40 | ! 41 | ip tcp synwait-time 5 42 | ! 43 | ! 44 | ! 45 | ! 46 | ! 47 | ! 48 | ! 49 | ! 50 | ! 51 | ! 52 | ! 53 | ! 54 | interface Loopback0 55 | ip address 3.1.1.1 255.255.255.255 56 | ! 57 | interface Ethernet0/0 58 | no ip address 59 | shutdown 60 | duplex auto 61 | ! 62 | interface GigabitEthernet0/0 63 | ip address 3.0.1.1 255.255.255.0 64 | media-type gbic 65 | speed 1000 66 | duplex full 67 | negotiation auto 68 | ! 69 | interface GigabitEthernet1/0 70 | ip address 10.23.21.3 255.255.255.0 71 | negotiation auto 72 | ! 73 | router ospf 1 74 | router-id 3.1.1.1 75 | redistribute connected subnets 76 | network 3.0.0.0 0.255.255.255 area 1 77 | ! 78 | router bgp 3 79 | bgp router-id 3.1.1.1 80 | bgp log-neighbor-changes 81 | neighbor as1 peer-group 82 | neighbor as1 remote-as 1 83 | neighbor as2 peer-group 84 | neighbor as2 remote-as 2 85 | neighbor as3 peer-group 86 | neighbor as3 remote-as 3 87 | neighbor 3.10.1.1 peer-group as3 88 | neighbor 3.10.1.1 update-source Loopback0 89 | neighbor 10.23.21.2 peer-group as2 90 | ! 91 | address-family ipv4 92 | bgp dampening 93 | bgp additional-paths select all 94 | bgp additional-paths send receive 95 | network 3.0.1.0 mask 255.255.255.0 96 | network 3.0.2.0 mask 255.255.255.0 97 | neighbor as1 send-community 98 | neighbor as1 route-map as1_to_as3 in 99 | neighbor as1 route-map as3_to_as1 out 100 | neighbor as2 send-community 101 | neighbor as2 route-map as2_to_as3 in 102 | neighbor as2 route-map as3_to_as2 out 103 | neighbor as3 send-community 104 | neighbor as3 advertise additional-paths all 105 | neighbor 3.10.1.1 activate 106 | neighbor 10.23.21.2 activate 107 | maximum-paths eibgp 5 108 | exit-address-family 109 | ! 110 | ip forward-protocol nd 111 | ! 112 | ip bgp-community new-format 113 | ip community-list expanded as1_community permit _1: 114 | ip community-list expanded as2_community permit _2: 115 | ip community-list expanded as3_community permit _3: 116 | ! 117 | no ip http server 118 | no ip http secure-server 119 | ! 120 | ! 121 | ip prefix-list default_list seq 5 permit 0.0.0.0/0 122 | ! 123 | ip prefix-list inbound_route_filter seq 5 deny 3.0.0.0/8 le 32 124 | ip prefix-list inbound_route_filter seq 10 permit 0.0.0.0/0 le 32 125 | access-list 101 permit ip host 1.0.1.0 host 255.255.255.0 126 | access-list 101 permit ip host 1.0.2.0 host 255.255.255.0 127 | access-list 102 permit ip host 2.0.0.0 host 255.0.0.0 128 | access-list 102 permit ip host 2.128.0.0 host 255.255.0.0 129 | access-list 103 permit ip host 3.0.1.0 host 255.255.255.0 130 | access-list 103 permit ip host 3.0.2.0 host 255.255.255.0 131 | ! 132 | route-map as3_to_as1 permit 2 133 | match ip address 102 134 | set metric 50 135 | set community 3:1 additive 136 | ! 137 | route-map as3_to_as1 permit 3 138 | match ip address 103 139 | set metric 50 140 | set community 3:1 additive 141 | ! 142 | route-map as1_to_as3 permit 100 143 | match community as1_community 144 | set local-preference 350 145 | ! 146 | route-map as3_to_as2 permit 1 147 | match ip address 101 148 | set metric 50 149 | set community 3:2 additive 150 | ! 151 | route-map as3_to_as2 permit 3 152 | match ip address 103 153 | set metric 50 154 | set community 3:2 additive 155 | ! 156 | route-map as3_to_as2 permit 5 157 | match ip address prefix-list default_list 158 | set metric 50 159 | set community 3:2 additive 160 | ! 161 | route-map as2_to_as3 permit 100 162 | match community as2_community 163 | set local-preference 350 164 | ! 165 | ! 166 | ! 167 | control-plane 168 | ! 169 | ! 170 | line con 0 171 | exec-timeout 0 0 172 | privilege level 15 173 | logging synchronous 174 | stopbits 1 175 | line aux 0 176 | exec-timeout 0 0 177 | privilege level 15 178 | logging synchronous 179 | stopbits 1 180 | line vty 0 4 181 | login 182 | ! 183 | ! 184 | end 185 | -------------------------------------------------------------------------------- /docs/common.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # (c) 2012, Jan-Piet Mens 3 | # 4 | # This file is part of Ansible 5 | # 6 | # Modified to support stand-alone Galaxy documentation 7 | # Copyright (c) 2014, 2017-2018 Juniper Networks Inc. 8 | # 2014, Rick Sherman 9 | # 10 | # Modified to support stand-alone Galaxy documentation for Batfish 11 | # Copyright (c) 2019 Intentionet, Inc. 12 | # 13 | # Ansible is free software: you can redistribute it and/or modify 14 | # it under the terms of the GNU General Public License as published by 15 | # the Free Software Foundation, either version 3 of the License, or 16 | # (at your option) any later version. 17 | # 18 | # Ansible is distributed in the hope that it will be useful, 19 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | # GNU General Public License for more details. 22 | # 23 | # You should have received a copy of the GNU General Public License 24 | # along with Ansible. If not, see . 25 | # 26 | 27 | import re 28 | 29 | from jinja2 import Environment, FileSystemLoader 30 | 31 | try: 32 | from html import escape as html_escape 33 | except ImportError: 34 | # Python-3.2 or later 35 | import cgi 36 | 37 | def html_escape(text, quote=True): 38 | return cgi.escape(text, quote) 39 | 40 | __all__ = ['jinja2_environment', 'html_ify', 'rst_fmt', 'rst_ify', 'rst_xline'] 41 | 42 | 43 | _ITALIC = re.compile(r"I\(([^)]+)\)") 44 | _BOLD = re.compile(r"B\(([^)]+)\)") 45 | _MODULE = re.compile(r"M\(([^)]+)\)") 46 | _URL_W_TEXT = re.compile(r"U\(([^)^|]+)\|([^)]+)\)") 47 | _URL = re.compile(r"U\(([^)^|]+)\)") 48 | _CONST = re.compile(r"C\(([^)]+)\)") 49 | _UNDERSCORE = re.compile(r"_") 50 | 51 | 52 | def module_to_html(matchobj): 53 | if matchobj.group(1) is not None: 54 | module_name = matchobj.group(1) 55 | module_href = _UNDERSCORE.sub('-', module_name) 56 | return '' + \ 57 | module_name + '' 58 | return '' 59 | 60 | 61 | def html_ify(text): 62 | ''' convert symbols like I(this is in italics) to valid HTML ''' 63 | 64 | t = html_escape(text) 65 | t = _ITALIC.sub("" + r"\1" + "", t) 66 | t = _BOLD.sub("" + r"\1" + "", t) 67 | t = _MODULE.sub(module_to_html, t) 68 | t = _URL_W_TEXT.sub("" + r"\1" + "", t) 69 | t = _URL.sub("" + r"\1" + "", t) 70 | t = _CONST.sub("" + r"\1" + "", t) 71 | 72 | return t 73 | 74 | 75 | def rst_xline(width, char="="): 76 | ''' return a restructured text line of a given length ''' 77 | 78 | return char * width 79 | 80 | 81 | def rst_ify(text): 82 | ''' convert symbols like I(this is in italics) to valid restructured text ''' 83 | 84 | try: 85 | t = _ITALIC.sub(r'*' + r"\1" + r"*", text) 86 | t = _BOLD.sub(r'**' + r"\1" + r"**", t) 87 | t = _MODULE.sub(r':ref:`' + r"\1 <\1>" + r"`", t) 88 | t = _URL_W_TEXT.sub(r'`' + r"\1" + r" <" + r"\2" + r">`_", t) 89 | t = _URL.sub(r'`' + r"\1" + r" <" + r"\1" + r">`_", t) 90 | t = _CONST.sub(r'``' + r"\1" + r"``", t) 91 | except Exception as e: 92 | raise AnsibleError("Could not process (%s) : %s" % (str(text), str(e))) 93 | 94 | return t 95 | 96 | 97 | def rst_fmt(text, fmt): 98 | ''' helper for Jinja2 to do format strings ''' 99 | 100 | return fmt % (text) 101 | 102 | 103 | def jinja2_environment(template_dir, template_type, template_file_name): 104 | 105 | env = Environment(loader=FileSystemLoader(template_dir), 106 | variable_start_string="@{", 107 | variable_end_string="}@", 108 | trim_blocks=True, 109 | ) 110 | env.globals['xline'] = rst_xline 111 | 112 | if template_type == 'rst': 113 | env.filters['convert_symbols_to_format'] = rst_ify 114 | env.filters['html_ify'] = html_ify 115 | env.filters['fmt'] = rst_fmt 116 | env.filters['xline'] = rst_xline 117 | template = env.get_template(template_file_name) 118 | outputname = "%s.rst" 119 | else: 120 | raise Exception("unknown format type: %s" % template_type) 121 | 122 | return env, template, outputname 123 | 124 | -------------------------------------------------------------------------------- /docs/bf_session.rst: -------------------------------------------------------------------------------- 1 | .. _bf_session: 2 | 3 | bf_session 4 | ++++++++++ 5 | Builds a session for use with other Batfish Ansible modules 6 | 7 | .. contents:: 8 | :local: 9 | :depth: 2 10 | 11 | 12 | Synopsis 13 | -------- 14 | 15 | 16 | * Builds a session for use with other Batfish Ansible modules and populates ``bf_session`` fact. 17 | 18 | 19 | 20 | Requirements 21 | ------------ 22 | The following software packages must be installed on hosts that execute this module: 23 | 24 | * pybatfish 25 | 26 | 27 | 28 | .. _module-specific-options-label: 29 | 30 | Module-specific Options 31 | ----------------------- 32 | The following options may be specified for this module: 33 | 34 | .. raw:: html 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 74 | 75 | 76 |
parametertyperequireddefaultcomments
host
stryes 52 |
Host (resolvable name or IP address) running the Batfish service.
53 |
name
strno"default" 62 |
Name of the session.
63 |
parameters
dictnoNone 72 |
Dictionary with additional parameters used to configure the session. Use {ssl: true} to use SSL.
73 |
77 |
78 | 79 | .. _bf_session-examples-label: 80 | 81 | Examples 82 | -------- 83 | 84 | :: 85 | 86 | 87 | # Establish session with Batfish service running on localhost 88 | - bf_session: 89 | host: localhost 90 | name: my_session 91 | # Establish SSL session with Batfish service running at 10.10.10.10 92 | - bf_session: 93 | host: 10.10.10.10 94 | name: my_session 95 | parameters: 96 | ssl: true 97 | # Establish SSL session with Batfish Enterprise service running at 10.10.10.10 98 | - bf_session: 99 | host: 10.10.10.10 100 | name: enterprise_session 101 | parameters: 102 | ssl: true 103 | session_type: bfe 104 | 105 | 106 | 107 | Return Values 108 | ------------- 109 | 110 | .. raw:: html 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 163 | 164 | 165 | 166 | 167 | 170 | 171 | 172 | 173 | 174 |
namedescriptionreturnedtype
session 125 |
Details about the created session.
126 |
alwayscomplex
contains: 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 157 | 158 | 159 | 160 | 161 |
namedescriptionreturnedtype
host 146 |
Host where service is hosted
147 |
alwaysstr
parameters 155 |
Additional parameters to connect to the service
156 |
if supplied by userdict
162 |
summary 168 |
Summary of action(s) performed.
169 |
alwaysstr
175 |
176 |
177 | 178 | 179 | 180 | 181 | 182 | Status 183 | ~~~~~~ 184 | 185 | This module is flagged as **preview** which means that it is not guaranteed to have a backwards compatible interface. 186 | 187 | 188 | -------------------------------------------------------------------------------- /tutorials/networks/example/configs/as1border1.cfg: -------------------------------------------------------------------------------- 1 | 2 | ! 3 | version 15.2 4 | service timestamps debug datetime msec 5 | service timestamps log datetime msec 6 | ! 7 | hostname as1border1 8 | ! 9 | boot-start-marker 10 | boot-end-marker 11 | ! 12 | ! 13 | ! 14 | no aaa new-model 15 | no ip icmp rate-limit unreachable 16 | ip cef 17 | ! 18 | ! 19 | ! 20 | ! 21 | ! 22 | ! 23 | no ip domain lookup 24 | ip domain name lab.local 25 | no ipv6 cef 26 | ! 27 | ! 28 | multilink bundle-name authenticated 29 | ! 30 | ! 31 | ! 32 | ! 33 | ! 34 | ! 35 | ! 36 | ! 37 | ! 38 | ip tcp synwait-time 5 39 | ! 40 | ! 41 | ! 42 | ! 43 | ! 44 | ! 45 | ! 46 | ! 47 | ! 48 | ! 49 | ! 50 | ! 51 | interface Loopback0 52 | ip address 1.1.1.1 255.255.255.255 53 | ! 54 | interface Ethernet0/0 55 | no ip address 56 | shutdown 57 | duplex auto 58 | ! 59 | interface GigabitEthernet0/0 60 | ip address 1.0.1.1 255.255.255.0 61 | media-type gbic 62 | speed 1000 63 | duplex full 64 | negotiation auto 65 | ! 66 | interface GigabitEthernet1/0 67 | ip address 10.12.11.1 255.255.255.0 68 | negotiation auto 69 | ! 70 | router ospf 1 71 | router-id 1.1.1.1 72 | redistribute connected subnets 73 | passive-interface Loopback0 74 | network 1.0.0.0 0.255.255.255 area 1 75 | ! 76 | router bgp 1 77 | bgp router-id 1.1.1.1 78 | bgp log-neighbor-changes 79 | neighbor as1 peer-group 80 | neighbor as1 remote-as 1 81 | neighbor as2 peer-group 82 | neighbor as2 remote-as 2 83 | neighbor as3 peer-group 84 | neighbor as3 remote-as 3 85 | neighbor xanadu peer-group 86 | neighbor xanadu remote-as 555 87 | neighbor bad-ebgp peer-group 88 | neighbor bad-ebgp remote-as 666 89 | neighbor 1.10.1.1 peer-group as1 90 | neighbor 1.10.1.1 update-source Loopback0 91 | neighbor 3.2.2.2 peer-group bad-ebgp 92 | neighbor 5.6.7.8 peer-group xanadu 93 | neighbor 10.12.11.2 peer-group as2 94 | ! 95 | address-family ipv4 96 | bgp dampening 97 | bgp additional-paths select all 98 | bgp additional-paths send receive 99 | network 1.0.1.0 mask 255.255.255.0 100 | network 1.0.2.0 mask 255.255.255.0 101 | neighbor as1 send-community 102 | neighbor as1 advertise additional-paths all 103 | neighbor as2 send-community 104 | neighbor as2 route-map as2_to_as1 in 105 | neighbor as2 route-map as1_to_as2 out 106 | neighbor as3 send-community 107 | neighbor as3 route-map as3_to_as1 in 108 | neighbor as3 route-map as1_to_as3 out 109 | neighbor 1.10.1.1 activate 110 | neighbor 3.2.2.2 activate 111 | neighbor 5.6.7.8 activate 112 | neighbor 10.12.11.2 activate 113 | maximum-paths eibgp 5 114 | exit-address-family 115 | ! 116 | ip forward-protocol nd 117 | ! 118 | ip bgp-community new-format 119 | ip community-list expanded as1_community permit _1: 120 | ip community-list expanded as2_community permit _2: 121 | ip community-list expanded as3_community permit _3: 122 | ! 123 | no ip http server 124 | no ip http secure-server 125 | ! 126 | ! 127 | ip prefix-list default_list seq 5 permit 0.0.0.0/0 128 | ! 129 | ip prefix-list inbound_route_filter seq 5 deny 1.0.0.0/8 le 32 130 | ip prefix-list inbound_route_filter seq 10 permit 0.0.0.0/0 le 32 131 | access-list 101 permit ip host 1.0.1.0 host 255.255.255.0 132 | access-list 101 permit ip host 1.0.2.0 host 255.255.255.0 133 | access-list 102 permit ip host 2.0.0.0 host 255.0.0.0 134 | access-list 102 permit ip host 2.128.0.0 host 255.255.0.0 135 | access-list 103 permit ip host 3.0.1.0 host 255.255.255.0 136 | access-list 103 permit ip host 3.0.2.0 host 255.255.255.0 137 | ! 138 | route-map as1_to_as2 permit 1 139 | match ip address 101 140 | set metric 50 141 | set community 1:2 additive 142 | ! 143 | route-map as1_to_as2 permit 3 144 | match ip address 103 145 | set metric 50 146 | set community 1:2 additive 147 | ! 148 | route-map as1_to_as2 permit 5 149 | match ip address prefix-list default_list 150 | set metric 50 151 | set community 1:2 additive 152 | ! 153 | route-map as2_to_as1 permit 100 154 | match community as2_community 155 | set local-preference 350 156 | ! 157 | route-map as1_to_as3 permit 1 158 | match ip address 101 159 | set metric 50 160 | set community 1:3 additive 161 | ! 162 | route-map as1_to_as3 permit 2 163 | match ip address 102 164 | set metric 50 165 | set community 1:3 additive 166 | ! 167 | route-map as3_to_as1 permit 100 168 | match community as3_community 169 | set local-preference 350 170 | ! 171 | ! 172 | ! 173 | control-plane 174 | ! 175 | ! 176 | line con 0 177 | exec-timeout 0 0 178 | privilege level 15 179 | logging synchronous 180 | stopbits 1 181 | line aux 0 182 | exec-timeout 0 0 183 | privilege level 15 184 | logging synchronous 185 | stopbits 1 186 | line vty 0 4 187 | login 188 | ! 189 | ! 190 | end 191 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ansible roles for Batfish 2 | 3 | Intentionet has created this Ansible role to allow users to embed [Batfish](https://www.github.com/batfish/batfish) or [Batfish Enterprise](https://www.intentionet.com/product/batfish-enterprise/) pre-deployment validation into any Ansible playbook. This role is hosted on Ansible Galaxy as `batfish.base`. The role includes a set of Ansible modules that analyze configuration files for an entire (or subset of a) network, allowing users to extract configuration data and perform network-wide validation tests in a completely vendor agnostic manner. 4 | 5 | ## Overview of Modules 6 | 7 | Some of the modules included in the role are: 8 | 9 | * **[bf_session](docs/bf_session.rst)** - Setup the connection to the server running Batfish or Batfish Enterprise 10 | 11 | * **[bf_init_snapshot](docs/bf_init_snapshot.rst)** - Initialize a network snapshot 12 | 13 | * **[bf_extract_facts](docs/bf_extract_facts.rst)** - Retrieve configuration facts for devices in the snapshot 14 | 15 | * **[bf_validate_facts](docs/bf_validate_facts.rst)** - Validate configuration facts for devices in the snapshot 16 | 17 | * **[bf_assert](docs/bf_assert.rst)** - Validate network behavior 18 | 19 | See [docs](docs) for a complete list of modules and their documentation, and [instructions for packaging your network snapshots](https://pybatfish.readthedocs.io/en/latest/notebooks/interacting.html#Packaging-snapshot-data) 20 | 21 | ## Examples 22 | The example playbook below outlines how to use the `batfish.base` role to extract the list of interfaces for all devices in the network. Check out the [tutorials](tutorials) for additional examples. 23 | 24 | 25 | ```yaml 26 | --- 27 | - name: Extract network device facts using Batfish and Ansible 28 | hosts: localhost 29 | connection: local 30 | gather_facts: no 31 | roles: 32 | - batfish.base 33 | 34 | tasks: 35 | 36 | - name: Setup connection to Batfish service 37 | bf_session: 38 | host: localhost 39 | name: local_batfish 40 | 41 | - name: Initialize the example network 42 | bf_init_snapshot: 43 | network: example_network 44 | snapshot: example_snapshot 45 | snapshot_data: ../networks/example 46 | overwrite: true 47 | 48 | - name: Retrieve Batfish Facts 49 | bf_extract_facts: 50 | output_directory: data/bf_facts 51 | register: bf_facts 52 | 53 | - name: Display configuration for all interfaces on all nodes 54 | debug: msg=" {{item.value.Interfaces}} " 55 | with_dict: "{{bf_facts.result.nodes}}" 56 | loop_control: 57 | label: " {{item.key}}.Interfaces " 58 | when: bf_facts.failed|bool == false 59 | ``` 60 | 61 | Note: to connect to a Batfish Enterprise service, just add `session_type: bfe` under `parameters:` in the setup task, e.g.: 62 | ```yaml 63 | - name: Setup connection to Batfish Enterprise service 64 | bf_session: 65 | host: localhost 66 | name: local_batfish 67 | parameters: 68 | session_type: bfe 69 | ``` 70 | 71 | ## Dependencies 72 | 73 | This module requires the following packages to be installed on the Ansible control machine: 74 | 75 | - Python 3.6 or 3.7 76 | - Ansible >=2.7 <=2.9.9 77 | - Batfish module requirements listed in `requirements.txt` 78 | 79 | - To install these requirements, run: 80 | ``` 81 | python3 -m pip install -r https://raw.githubusercontent.com/batfish/ansible/master/requirements.txt 82 | ``` 83 | 84 | - Batfish service and client 85 | - For open-source users: to install Batfish and Pybatfish, you may use the [batfish setup playbook](tutorials/playbooks/batfish_setup.yml) or run the following commands to update and run Batfish: 86 | ``` 87 | python3 -m pip install --upgrade pybatfish 88 | docker pull batfish/allinone 89 | docker run -v batfish-data:/data -p 8888:8888 -p 9997:9997 -p 9996:9996 batfish/allinone 90 | ``` 91 | 92 | - For enterprise users: follow the instructions delivered with Batfish Enterprise 93 | 94 | ## Installation 95 | 96 | Ensure that the [dependencies](#dependencies) above are met, and then get the latest version of the role from Ansible galaxy. 97 | 98 | ``` 99 | ansible-galaxy install --force batfish.base 100 | ``` 101 | 102 | 103 | ## License 104 | Apache 2.0 105 | 106 | ## Support 107 | For bug reports and feature requests, you may: 108 | 109 | - Open a Github [issue](https://github.com/batfish/ansible/issues) 110 | - Join our [Slack Group](https://join.slack.com/t/batfish-org/shared_invite/enQtMzA0Nzg2OTAzNzQ1LTcyYzY3M2Q0NWUyYTRhYjdlM2IzYzRhZGU1NWFlNGU2MzlhNDY3OTJmMDIyMjQzYmRlNjhkMTRjNWIwNTUwNTQ) and post a question 111 | 112 | ## Contributors 113 | Intentionet is contributing to and maintaining this repository. 114 | -------------------------------------------------------------------------------- /docs/bf_assert.rst: -------------------------------------------------------------------------------- 1 | .. _bf_assert: 2 | 3 | bf_assert 4 | +++++++++ 5 | Makes assertions about a Batfish snapshot 6 | 7 | .. contents:: 8 | :local: 9 | :depth: 2 10 | 11 | 12 | Synopsis 13 | -------- 14 | 15 | 16 | * Makes assertions about the contents and/or behavior of a Batfish snapshot. 17 | 18 | 19 | 20 | Requirements 21 | ------------ 22 | The following software packages must be installed on hosts that execute this module: 23 | 24 | * pybatfish 25 | 26 | 27 | 28 | .. _module-specific-options-label: 29 | 30 | Module-specific Options 31 | ----------------------- 32 | The following options may be specified for this module: 33 | 34 | .. raw:: html 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 85 | 86 | 87 |
parametertyperequireddefaultcomments
assertions
listyes 52 |
List of assertions to make about the snapshot.
53 |
See assertions.rst for documentation of supported assertions.
54 |
network
strnoValue in the bf_network fact. 63 |
Name of the network to make assertions about.
64 |
session
dictnoValue in the bf_session fact. 73 |
Batfish session object required to connect to the Batfish service.
74 |
snapshot
strnoValue in the bf_snapshot fact. 83 |
Name of the snapshot to make assertions about.
84 |
88 |
89 | 90 | .. _bf_assert-examples-label: 91 | 92 | Examples 93 | -------- 94 | 95 | :: 96 | 97 | 98 | # Confirm there are no undefined references or incompatible BGP sessions 99 | - bf_assert: 100 | assertions: 101 | - type: assert_no_undefined_references 102 | name: Confirm we have no undefined references 103 | - type: assert_no_incompatible_bgp_sessions 104 | name: Confirm we have no incompatible BGP sessions 105 | 106 | # Confirm 10.10.10.10 is reachable by traffic entering Gig0/0 of as1border1 107 | - bf_assert: 108 | assertions: 109 | - type: assert_all_flows_succeed 110 | name: confirm host is reachable for traffic received on GigEth0/0 111 | parameters: 112 | startLocation: '@enter(as1border1[GigabitEthernet0/0])' 113 | headers: 114 | dstIps: '10.10.10.10' 115 | 116 | # Confirm a filter denies some specific traffic 117 | - bf_assert: 118 | assertions: 119 | - type: assert_filter_denies 120 | name: confirm node1 filter block_access denies TCP traffic on port 22 121 | parameters: 122 | filters: 'node1["block_access"]' 123 | headers: 124 | applications: 'ssh' 125 | 126 | 127 | 128 | Return Values 129 | ------------- 130 | 131 | .. raw:: html 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 157 | 158 | 159 | 160 | 161 |
namedescriptionreturnedtype
result 146 |
List of assertion results. There is one entry per assertion, and each entry contains details of the assertion and additional information when the assertion fails.
147 |
alwayslist
summary 155 |
Summary of action(s) performed.
156 |
alwaysstr
162 |
163 |
164 | 165 | 166 | 167 | 168 | 169 | Status 170 | ~~~~~~ 171 | 172 | This module is flagged as **preview** which means that it is not guaranteed to have a backwards compatible interface. 173 | 174 | 175 | -------------------------------------------------------------------------------- /tutorials/networks/example/configs/as2border2.cfg: -------------------------------------------------------------------------------- 1 | 2 | ! 3 | version 15.2 4 | service timestamps debug datetime msec 5 | service timestamps log datetime msec 6 | ! 7 | hostname as2border2 8 | ! 9 | boot-start-marker 10 | boot-end-marker 11 | ! 12 | ! 13 | ntp server 18.18.18.18 14 | ! 15 | ! 16 | no aaa new-model 17 | no ip icmp rate-limit unreachable 18 | ip cef 19 | ! 20 | ! 21 | ! 22 | ! 23 | ! 24 | ! 25 | no ip domain lookup 26 | ip domain name lab.local 27 | no ipv6 cef 28 | ! 29 | ! 30 | multilink bundle-name authenticated 31 | ! 32 | ! 33 | ! 34 | ! 35 | ! 36 | ! 37 | ! 38 | ! 39 | ! 40 | ip tcp synwait-time 5 41 | ! 42 | ! 43 | ! 44 | ! 45 | ! 46 | ! 47 | ! 48 | ! 49 | ! 50 | ! 51 | ! 52 | ! 53 | interface Loopback0 54 | ip address 2.1.1.2 255.255.255.255 55 | ! 56 | interface Ethernet0/0 57 | no ip address 58 | shutdown 59 | duplex auto 60 | ! 61 | interface GigabitEthernet0/0 62 | ip address 10.23.21.2 255.255.255.0 63 | ip access-group OUTSIDE_TO_INSIDE in 64 | ip access-group INSIDE_TO_AS3 out 65 | media-type gbic 66 | speed 1000 67 | duplex full 68 | negotiation auto 69 | ! 70 | interface GigabitEthernet1/0 71 | ip address 2.12.22.1 255.255.255.0 72 | negotiation auto 73 | ! 74 | interface GigabitEthernet2/0 75 | ip address 2.12.21.1 255.255.255.0 76 | negotiation auto 77 | ! 78 | router ospf 1 79 | router-id 2.1.1.2 80 | redistribute connected subnets 81 | network 2.0.0.0 0.255.255.255 area 1 82 | ! 83 | router bgp 2 84 | bgp router-id 2.1.1.2 85 | bgp log-neighbor-changes 86 | neighbor as1 peer-group 87 | neighbor as1 remote-as 1 88 | neighbor as2 peer-group 89 | neighbor as2 remote-as 2 90 | neighbor as3 peer-group 91 | neighbor as3 remote-as 3 92 | neighbor 2.1.2.1 peer-group as2 93 | neighbor 2.1.2.1 update-source Loopback0 94 | neighbor 2.1.2.2 peer-group as2 95 | neighbor 2.1.2.2 update-source Loopback0 96 | neighbor 10.23.21.3 peer-group as3 97 | ! 98 | address-family ipv4 99 | bgp dampening 100 | bgp additional-paths select all 101 | bgp additional-paths send receive 102 | aggregate-address 2.128.0.0 255.255.0.0 summary-only 103 | neighbor as1 send-community 104 | neighbor as1 route-map as1_to_as2 in 105 | neighbor as1 route-map as2_to_as1 out 106 | neighbor as2 send-community 107 | neighbor as2 advertise additional-paths all 108 | neighbor as3 send-community 109 | neighbor as3 route-map as3_to_as2 in 110 | neighbor as3 route-map as2_to_as3 out 111 | neighbor 2.1.2.1 activate 112 | neighbor 2.1.2.2 activate 113 | neighbor 10.23.21.3 activate 114 | maximum-paths eibgp 5 115 | exit-address-family 116 | ! 117 | ip forward-protocol nd 118 | ! 119 | ip bgp-community new-format 120 | ip community-list expanded as1_community permit _1: 121 | ip community-list expanded as2_community permit _2: 122 | ip community-list expanded as3_community permit _3: 123 | ! 124 | no ip http server 125 | no ip http secure-server 126 | ! 127 | ip access-list extended INSIDE_TO_AS3 128 | permit ip 2.0.0.0 0.255.255.255 3.0.0.0 0.255.255.255 129 | permit ip 10.23.21.2 0.0.0.0 10.23.21.3 0.0.0.0 130 | deny ip any any 131 | ip access-list extended OUTSIDE_TO_INSIDE 132 | deny ip 2.0.0.0 0.255.255.255 any 133 | permit ip any any 134 | ! 135 | ! 136 | ip prefix-list inbound_route_filter seq 5 deny 2.0.0.0/8 le 32 137 | ip prefix-list inbound_route_filter seq 10 permit 0.0.0.0/0 le 32 138 | ! 139 | ip prefix-list outbound_routes seq 5 permit 2.128.0.0/9 ge 16 140 | access-list 101 permit ip host 1.0.1.0 host 255.255.255.0 141 | access-list 101 permit ip host 1.0.2.0 host 255.255.255.0 142 | access-list 103 permit ip host 3.0.1.0 host 255.255.255.0 143 | access-list 103 permit ip host 3.0.2.0 host 255.255.255.0 144 | ! 145 | route-map as2_to_as1 permit 2 146 | match ip address prefix-list outbound_routes 147 | set metric 50 148 | set community 2:1 additive 149 | ! 150 | route-map as2_to_as1 permit 3 151 | match ip address 103 152 | set metric 50 153 | set community 2:1 additive 154 | ! 155 | route-map as1_to_as2 permit 100 156 | match community as1_community 157 | set local-preference 350 158 | set community 1:2 additive 159 | ! 160 | route-map as2_to_as3 permit 1 161 | match ip address 101 162 | set metric 50 163 | set community 2:3 additive 164 | ! 165 | route-map as2_to_as3 permit 2 166 | match ip address prefix-list outbound_routes 167 | set metric 50 168 | set community 2:3 additive 169 | ! 170 | route-map as3_to_as2 permit 100 171 | match community as3_community 172 | set local-preference 350 173 | set community 3:2 additive 174 | ! 175 | ! 176 | ! 177 | control-plane 178 | ! 179 | ! 180 | line con 0 181 | exec-timeout 0 0 182 | privilege level 15 183 | logging synchronous 184 | stopbits 1 185 | line aux 0 186 | exec-timeout 0 0 187 | privilege level 15 188 | logging synchronous 189 | stopbits 1 190 | line vty 0 4 191 | login 192 | ! 193 | ! 194 | end 195 | -------------------------------------------------------------------------------- /tutorials/networks/example/configs/as1border2.cfg: -------------------------------------------------------------------------------- 1 | 2 | ! 3 | version 15.2 4 | service timestamps debug datetime msec 5 | service timestamps log datetime msec 6 | ! 7 | hostname as1border2 8 | ! 9 | boot-start-marker 10 | boot-end-marker 11 | ! 12 | ! 13 | ntp server 18.18.18.18 14 | ntp server 23.23.23.23 15 | ! 16 | ! 17 | no aaa new-model 18 | no ip icmp rate-limit unreachable 19 | ip cef 20 | ! 21 | ! 22 | ! 23 | ! 24 | ! 25 | ! 26 | no ip domain lookup 27 | ip domain name lab.local 28 | no ipv6 cef 29 | ! 30 | ! 31 | multilink bundle-name authenticated 32 | ! 33 | ! 34 | ! 35 | ! 36 | ! 37 | ! 38 | ! 39 | ! 40 | ! 41 | ip tcp synwait-time 5 42 | ! 43 | ! 44 | ! 45 | ! 46 | ! 47 | ! 48 | ! 49 | ! 50 | ! 51 | ! 52 | ! 53 | ! 54 | interface Loopback0 55 | ip address 1.2.2.2 255.255.255.255 56 | ! 57 | interface Ethernet0/0 58 | no ip address 59 | shutdown 60 | duplex auto 61 | ! 62 | interface GigabitEthernet0/0 63 | ip address 10.13.22.1 255.255.255.0 64 | media-type gbic 65 | speed 1000 66 | duplex full 67 | negotiation auto 68 | ! 69 | interface GigabitEthernet1/0 70 | ip address 1.0.2.1 255.255.255.0 71 | negotiation auto 72 | ! 73 | interface GigabitEthernet2/0 74 | ip address 10.14.22.1 255.255.255.0 75 | negotiation auto 76 | ! 77 | router ospf 1 78 | router-id 1.2.2.2 79 | redistribute connected subnets 80 | network 1.0.0.0 0.255.255.255 area 1 81 | ! 82 | router bgp 1 83 | bgp router-id 1.2.2.2 84 | bgp log-neighbor-changes 85 | neighbor as1 peer-group 86 | neighbor as1 remote-as 1 87 | neighbor as2 peer-group 88 | neighbor as2 remote-as 2 89 | neighbor as3 peer-group 90 | neighbor as3 remote-as 3 91 | neighbor as4 peer-group 92 | neighbor as4 remote-as 4 93 | neighbor 1.10.1.1 peer-group as1 94 | neighbor 1.10.1.1 update-source Loopback0 95 | neighbor 10.13.22.3 peer-group as3 96 | neighbor 10.14.22.4 peer-group as4 97 | ! 98 | address-family ipv4 99 | bgp dampening 100 | bgp additional-paths select all 101 | bgp additional-paths send receive 102 | network 1.0.1.0 mask 255.255.255.0 103 | network 1.0.2.0 mask 255.255.255.0 104 | neighbor as1 send-community 105 | neighbor as1 advertise additional-paths all 106 | neighbor as2 send-community 107 | neighbor as2 route-map as2_to_as1 in 108 | neighbor as2 route-map as1_to_as2 out 109 | neighbor as3 send-community 110 | neighbor as3 route-map as3_to_as1 in 111 | neighbor as3 route-map as1_to_as3 out 112 | neighbor as4 route-map as4_to_as1 in 113 | neighbor as4 route-map as1_to_as4 out 114 | neighbor 1.10.1.1 activate 115 | neighbor 10.13.22.3 activate 116 | neighbor 10.14.22.4 activate 117 | maximum-paths eibgp 5 118 | exit-address-family 119 | ! 120 | ip forward-protocol nd 121 | ! 122 | ip bgp-community new-format 123 | ip community-list expanded as1_community permit _1: 124 | ip community-list expanded as2_community permit _2: 125 | ip community-list expanded as3_community permit _3: 126 | ip community-list expanded as4_community permit _4: 127 | ! 128 | no ip http server 129 | no ip http secure-server 130 | ! 131 | ! 132 | ip prefix-list as4-prefixes seq 1 permit 4.0.0.0/8 le 32 133 | ! 134 | ip prefix-list inbound_route_filter seq 5 deny 1.0.0.0/8 le 32 135 | ip prefix-list inbound_route_filter seq 10 permit 0.0.0.0/0 le 32 136 | access-list 101 permit ip host 1.0.1.0 host 255.255.255.0 137 | access-list 101 permit ip host 1.0.2.0 host 255.255.255.0 138 | access-list 102 permit ip host 2.0.0.0 host 255.0.0.0 139 | access-list 102 permit ip host 2.128.0.0 host 255.255.0.0 140 | access-list 103 permit ip host 3.0.1.0 host 255.255.255.0 141 | ! 142 | route-map as1_to_as2 permit 1 143 | match ip address 101 144 | set metric 50 145 | set community 1:2 additive 146 | ! 147 | route-map as1_to_as2 permit 3 148 | match ip address 103 149 | set metric 50 150 | set community 1:2 additive 151 | ! 152 | route-map as2_to_as1 permit 100 153 | match community as2_community 154 | set local-preference 350 155 | ! 156 | route-map as1_to_as3 permit 1 157 | match ip address 101 158 | set metric 50 159 | set community 1:3 additive 160 | ! 161 | route-map as1_to_as3 permit 2 162 | match ip address 102 163 | set metric 50 164 | set community 1:3 additive 165 | ! 166 | route-map as3_to_as1 permit 100 167 | match community as3_community 168 | set local-preference 350 169 | ! 170 | route-map as1_to_as4 permit 2 171 | set metric 50 172 | set community 1:4 additive 173 | ! 174 | route-map as4_to_as1 permit 100 175 | match ip address prefix-list as4-prefixes 176 | match community as4_community 177 | set local-preference 350 178 | ! 179 | ! 180 | ! 181 | control-plane 182 | ! 183 | ! 184 | line con 0 185 | exec-timeout 0 0 186 | privilege level 15 187 | logging synchronous 188 | stopbits 1 189 | line aux 0 190 | exec-timeout 0 0 191 | privilege level 15 192 | logging synchronous 193 | stopbits 1 194 | line vty 0 4 195 | login 196 | ! 197 | ! 198 | end 199 | -------------------------------------------------------------------------------- /docs/bf_upload_diagnostics.rst: -------------------------------------------------------------------------------- 1 | .. _bf_upload_diagnostics: 2 | 3 | bf_upload_diagnostics 4 | +++++++++++++++++++++ 5 | Upload anonymized diagnostic information about a Batfish snapshot 6 | 7 | .. contents:: 8 | :local: 9 | :depth: 2 10 | 11 | 12 | Synopsis 13 | -------- 14 | 15 | 16 | * Fetches, anonymizes, and uploads diagnostic information about a Batfish snapshot. This runs a series of diagnostic questions on the specified snapshot, which are then anonymized with Netconan (`https://github.com/intentionet/netconan `_), and optionally uploaded to the Batfish developers. By default, passwords are stripped and IP addresses are anonymized. 17 | 18 | 19 | 20 | Requirements 21 | ------------ 22 | The following software packages must be installed on hosts that execute this module: 23 | 24 | * pybatfish 25 | 26 | 27 | 28 | .. _module-specific-options-label: 29 | 30 | Module-specific Options 31 | ----------------------- 32 | The following options may be specified for this module: 33 | 34 | .. raw:: html 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 104 | 105 | 106 |
parametertyperequireddefaultcomments
contact_info
strno 52 |
Contact information associated with this upload.
53 |
dry_run
boolnoTrue 62 |
Whether or not to skip upload. If true, upload is skipped and the anonymized files will be stored locally for review. If false, anonymized files will be uploaded to the Batfish developers.
63 |
netconan_config
boolnoAnonymize passwords and IP addresses. 72 |
Path to Netconan (https://github.com/intentionet/netconan) configuration file, containing settings used for information anonymization.
73 |
network
strnoValue in the bf_network fact. 82 |
Name of the network to collect diagnostic information from.
83 |
session
dictnoValue in the bf_session fact. 92 |
Batfish session object required to connect to the Batfish service.
93 |
snapshot
strnoValue in the bf_snapshot fact. 102 |
Name of the snapshot to collect diagnostic information about.
103 |
107 |
108 | 109 | .. _bf_upload_diagnostics-examples-label: 110 | 111 | Examples 112 | -------- 113 | 114 | :: 115 | 116 | 117 | # Generate diagnostic information about the specified snapshot and save locally (do not upload) 118 | - bf_upload_diagnostics 119 | network: datacenter_sea 120 | snapshot: 2019-01-01 121 | dry_run: true 122 | contact_info: my.email@example.com 123 | # Generate diagnostic information about the specified snapshot and upload to the Batfish developers 124 | - bf_upload_diagnostics 125 | network: datacenter_sea 126 | snapshot: 2019-01-01 127 | dry_run: false 128 | contact_info: my.email@example.com 129 | 130 | 131 | 132 | Return Values 133 | ------------- 134 | 135 | .. raw:: html 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 152 | 153 | 154 | 155 | 156 |
namedescriptionreturnedtype
summary 150 |
Summary of action(s) performed.
151 |
alwaysstr
157 |
158 |
159 | 160 | 161 | 162 | 163 | 164 | Status 165 | ~~~~~~ 166 | 167 | This module is flagged as **preview** which means that it is not guaranteed to have a backwards compatible interface. 168 | 169 | 170 | -------------------------------------------------------------------------------- /tutorials/networks/example/configs/as2border1.cfg: -------------------------------------------------------------------------------- 1 | 2 | ! 3 | version 15.2 4 | service timestamps debug datetime msec 5 | service timestamps log datetime msec 6 | ! 7 | hostname as2border1 8 | ! 9 | boot-start-marker 10 | boot-end-marker 11 | ! 12 | ! 13 | ntp server 18.18.18.18 14 | ntp server 23.23.23.23 15 | ! 16 | ! 17 | no aaa new-model 18 | no ip icmp rate-limit unreachable 19 | ip cef 20 | ! 21 | ! 22 | ! 23 | ! 24 | ! 25 | ! 26 | no ip domain lookup 27 | ip domain name lab.local 28 | no ipv6 cef 29 | ! 30 | aaa new-model 31 | aaa authentication login privilege-mode 32 | ! 33 | multilink bundle-name authenticated 34 | ! 35 | ! 36 | ! 37 | ! 38 | ! 39 | ! 40 | ! 41 | ! 42 | ! 43 | ip tcp synwait-time 5 44 | ! 45 | ! 46 | ! 47 | ! 48 | ! 49 | ! 50 | ! 51 | ! 52 | ! 53 | ! 54 | ! 55 | ! 56 | interface Loopback0 57 | ip address 2.1.1.1 255.255.255.255 58 | ! 59 | interface Ethernet0/0 60 | no ip address 61 | shutdown 62 | duplex auto 63 | ! 64 | interface GigabitEthernet0/0 65 | ip address 10.12.11.2 255.255.255.0 66 | ip access-group OUTSIDE_TO_INSIDE in 67 | ip access-group INSIDE_TO_AS1 out 68 | media-type gbic 69 | speed 1000 70 | duplex full 71 | negotiation auto 72 | ! 73 | interface GigabitEthernet1/0 74 | ip address 2.12.11.1 255.255.255.0 75 | negotiation auto 76 | ! 77 | interface GigabitEthernet2/0 78 | ip address 2.12.12.1 255.255.255.0 79 | negotiation auto 80 | ! 81 | router ospf 1 82 | router-id 2.1.1.1 83 | redistribute connected subnets 84 | network 2.0.0.0 0.255.255.255 area 1 85 | ! 86 | router bgp 2 87 | bgp router-id 2.1.1.1 88 | bgp log-neighbor-changes 89 | neighbor as1 peer-group 90 | neighbor as1 remote-as 1 91 | neighbor as2 peer-group 92 | neighbor as2 remote-as 2 93 | neighbor as3 peer-group 94 | neighbor as3 remote-as 3 95 | neighbor 2.1.2.1 peer-group as2 96 | neighbor 2.1.2.1 update-source Loopback0 97 | neighbor 2.1.2.2 peer-group as2 98 | neighbor 2.1.2.2 update-source Loopback0 99 | neighbor 10.12.11.1 peer-group as1 100 | ! 101 | address-family ipv4 102 | bgp dampening 103 | bgp additional-paths select all 104 | bgp additional-paths send receive 105 | aggregate-address 2.128.0.0 255.255.0.0 summary-only 106 | neighbor as1 send-community 107 | neighbor as1 route-map as1_to_as2 in 108 | neighbor as1 route-map as2_to_as1 out 109 | neighbor as2 send-community 110 | neighbor as2 advertise additional-paths all 111 | neighbor as3 send-community 112 | neighbor as3 route-map as3_to_as2 in 113 | neighbor as3 route-map as2_to_as3 out 114 | neighbor 2.1.2.1 activate 115 | neighbor 2.1.2.2 activate 116 | neighbor 10.12.11.1 activate 117 | maximum-paths eibgp 5 118 | exit-address-family 119 | ! 120 | ip forward-protocol nd 121 | ! 122 | ip bgp-community new-format 123 | ip community-list expanded as1_community permit _1: 124 | ip community-list expanded as2_community permit _2: 125 | ip community-list expanded as3_community permit _3: 126 | ! 127 | no ip http server 128 | no ip http secure-server 129 | ! 130 | ip access-list extended INSIDE_TO_AS1 131 | permit ip 2.0.0.0 0.255.255.255 1.0.0.0 0.255.255.255 132 | permit ip 10.12.11.2 0.0.0.0 10.12.11.1 0.0.0.0 133 | deny ip any any 134 | ip access-list extended OUTSIDE_TO_INSIDE 135 | deny ip 2.0.0.0 0.255.255.255 any 136 | deny ip any host 2.128.1.101 137 | permit ip any any 138 | ! 139 | ! 140 | ip prefix-list inbound_route_filter seq 5 deny 2.0.0.0/8 le 32 141 | ip prefix-list inbound_route_filter seq 10 permit 0.0.0.0/0 le 32 142 | ! 143 | ip prefix-list outbound_routes seq 5 permit 2.128.0.0/9 ge 16 144 | access-list 101 permit ip host 1.0.1.0 host 255.255.255.0 145 | access-list 101 permit ip host 1.0.2.0 host 255.255.255.0 146 | access-list 103 permit ip host 3.0.1.0 host 255.255.255.0 147 | access-list 103 permit ip host 3.0.2.0 host 255.255.255.0 148 | ! 149 | route-map as2_to_as1 permit 2 150 | match ip address prefix-list outbound_routes 151 | set metric 50 152 | set community 2:1 additive 153 | ! 154 | route-map as2_to_as1 permit 3 155 | match ip address 103 156 | set metric 50 157 | set community 2:1 additive 158 | ! 159 | route-map as1_to_as2 permit 100 160 | match community as1_community 161 | set local-preference 350 162 | set community 1:2 additive 163 | ! 164 | route-map as2_to_as3 permit 1 165 | match ip address 101 166 | set metric 50 167 | set community 2:3 additive 168 | ! 169 | route-map as2_to_as3 permit 2 170 | match ip address prefix-list outbound_routes 171 | set metric 50 172 | set community 2:3 additive 173 | ! 174 | route-map as3_to_as2 permit 100 175 | match community as3_community 176 | set local-preference 350 177 | set community 3:2 additive 178 | ! 179 | ! 180 | ! 181 | control-plane 182 | ! 183 | ! 184 | line con 0 185 | exec-timeout 0 0 186 | privilege level 15 187 | logging synchronous 188 | stopbits 1 189 | line aux 0 190 | exec-timeout 0 0 191 | privilege level 15 192 | logging synchronous 193 | stopbits 1 194 | line vty 0 4 195 | login 196 | ! 197 | ! 198 | end 199 | -------------------------------------------------------------------------------- /action_plugins/bf_action_plugin_common.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # Copyright 2019 The Batfish Open Source Project 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | from __future__ import (absolute_import, division, print_function) 16 | 17 | import os 18 | 19 | __metaclass__ = type 20 | 21 | from ansible.errors import AnsibleActionFail, AnsibleUndefinedVariable 22 | from ansible.plugins.action import ActionBase 23 | from ansible.utils.display import Display 24 | 25 | display = Display() 26 | 27 | # Modules that require snapshot (and network) to be explicitly specified 28 | _EXPLICIT_SNAPSHOT_PARAMETER_MODULES = { 29 | 'bf_init_snapshot', 'bf_upload_diagnostics', 'bf_set_snapshot' 30 | } 31 | 32 | # Modules that perform assertions on snapshots 33 | _ASSERTION_MODULES = { 34 | 'bf_assert', 'bf_validate_facts' 35 | } 36 | 37 | 38 | class ActionModule(ActionBase): 39 | def run(self, tmp=None, task_vars=None): 40 | # Need to use local connect, since Batfish modules run on localhost only 41 | if self._play_context.connection != 'local': 42 | return dict( 43 | failed=True, 44 | msg='invalid connection specified, expected connection=local, ' 45 | 'got %s' % self._play_context.connection 46 | ) 47 | 48 | # Use user-specified session or ansible_facts.bf_session in that order 49 | facts = self._templar.template('{{ansible_facts}}') # .bf_session 50 | module_args = self._task.args.copy() 51 | 52 | # Fall-back to using values from Ansible facts for common module parameters 53 | if 'session' not in module_args: 54 | session = facts.get('bf_session') 55 | if session is None: 56 | raise AnsibleActionFail( 57 | 'No Batfish session detected. Run the bf_session module to set one up.') 58 | display.vvv( 59 | 'No session supplied, using session from Ansible facts: %s' % session) 60 | module_args['session'] = session 61 | 62 | module_name = self._task.action 63 | 64 | # Fall-back to using values from Ansible facts for snapshot/network 65 | # name for any module that doesn't require these to be explicitly set 66 | if module_name not in _EXPLICIT_SNAPSHOT_PARAMETER_MODULES: 67 | if 'snapshot' not in module_args: 68 | snapshot = facts.get('bf_snapshot') 69 | if snapshot is None: 70 | raise AnsibleActionFail( 71 | 'No Batfish snapshot detected. Run the bf_init_snapshot ' 72 | 'module to set one up or set the snapshot name via ' 73 | 'bf_set_snapshot module.') 74 | module_args['snapshot'] = snapshot 75 | 76 | if 'network' not in module_args: 77 | network = facts.get('bf_network') 78 | if network is None: 79 | raise AnsibleActionFail( 80 | 'No Batfish network detected. Run the bf_init_snapshot ' 81 | 'module to set one up or set the network name via ' 82 | 'bf_set_snapshot module.') 83 | module_args['network'] = network 84 | 85 | if module_name in _ASSERTION_MODULES: 86 | if 'bf_policy_name' not in os.environ: 87 | try: 88 | os.environ['bf_policy_name'] = self._templar.template( 89 | '{{ansible_play_name}}') 90 | except AnsibleUndefinedVariable: 91 | # If ansible_play_name isn't available, use the playbook's dir name 92 | os.environ['bf_policy_name'] = os.path.basename( 93 | self._templar.template( 94 | '{{playbook_dir}}')) 95 | # Differentiate between different runs of the same policy/play 96 | if 'bf_policy_id' not in os.environ: 97 | os.environ['bf_policy_id'] = self._play_context._uuid 98 | if 'bf_test_name' not in os.environ: 99 | os.environ['bf_test_name'] = self._task.name 100 | 101 | result = self._execute_module(module_name=module_name, 102 | module_args=module_args, 103 | task_vars=task_vars, 104 | wrap_async=self._task.async_val) 105 | return result 106 | -------------------------------------------------------------------------------- /docs/bf_extract_facts.rst: -------------------------------------------------------------------------------- 1 | .. _bf_extract_facts: 2 | 3 | bf_extract_facts 4 | ++++++++++++++++ 5 | Extracts facts for a Batfish snapshot 6 | 7 | .. contents:: 8 | :local: 9 | :depth: 2 10 | 11 | 12 | Synopsis 13 | -------- 14 | 15 | 16 | * Extracts and returns facts for a Batfish snapshot and saves them (one YAML file node) to the output directory if specified. 17 | 18 | 19 | 20 | Requirements 21 | ------------ 22 | The following software packages must be installed on hosts that execute this module: 23 | 24 | * pybatfish 25 | 26 | 27 | 28 | .. _module-specific-options-label: 29 | 30 | Module-specific Options 31 | ----------------------- 32 | The following options may be specified for this module: 33 | 34 | .. raw:: html 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 94 | 95 | 96 |
parametertyperequireddefaultcomments
network
strnoValue in the bf_network fact. 52 |
Name of the network to extract facts for.
53 |
nodes
strnoAll nodes 62 |
Nodes to extract facts for. See https://github.com/batfish/batfish/blob/master/questions/Parameters.md#node-specifier for more details on node specifiers.
63 |
output_directory
strnoNone 72 |
Directory to save facts to.
73 |
session
dictnoValue in the bf_session fact. 82 |
Batfish session object required to connect to the Batfish service.
83 |
snapshot
strnoValue in the bf_snapshot fact. 92 |
Name of the snapshot to extract facts for.
93 |
97 |
98 | 99 | .. _bf_extract_facts-examples-label: 100 | 101 | Examples 102 | -------- 103 | 104 | :: 105 | 106 | 107 | # Extract facts and save to an output directory 108 | - bf_extract_facts: 109 | output_directory: output/facts/ 110 | # Extract facts for nodes whose names contain as1border or host 111 | - bf_extract_facts: 112 | nodes: /as1border|host/ 113 | 114 | 115 | 116 | Return Values 117 | ------------- 118 | 119 | .. raw:: html 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 172 | 173 | 174 | 175 | 176 | 179 | 180 | 181 | 182 | 183 |
namedescriptionreturnedtype
result 134 |
Dictionary of extracted facts.
135 |
alwayscomplex
contains: 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 166 | 167 | 168 | 169 | 170 |
namedescriptionreturnedtype
nodes 155 |
Dictionary of node-name to node-facts for each node.
156 |
alwayscomplex
version 164 |
Fact-format version of the returned facts.
165 |
alwaysstr
171 |
summary 177 |
Summary of action(s) performed.
178 |
alwaysstr
184 |
185 |
186 | 187 | 188 | 189 | 190 | 191 | Status 192 | ~~~~~~ 193 | 194 | This module is flagged as **preview** which means that it is not guaranteed to have a backwards compatible interface. 195 | 196 | 197 | -------------------------------------------------------------------------------- /library/bf_set_snapshot.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # Copyright 2019 The Batfish Open Source Project 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | ANSIBLE_METADATA = { 17 | 'metadata_version': '1.1', 18 | 'status': ['preview'], 19 | 'supported_by': 'community' 20 | } 21 | 22 | # Note: these docs take into account the fact that the plugin handles supplying default values for some params 23 | DOCUMENTATION = ''' 24 | --- 25 | module: bf_set_snapshot 26 | short_description: Set the current Batfish snapshot 27 | version_added: "2.7" 28 | description: 29 | - Set the current Batfish network and snapshot. 30 | options: 31 | network: 32 | description: 33 | - Name of the network to set as the active network. 34 | required: true 35 | type: str 36 | snapshot: 37 | description: 38 | - Name of the snapshot to set as the active snapshot. This snapshot must already exist. 39 | required: true 40 | type: str 41 | session: 42 | description: 43 | - Batfish session object required to connect to the Batfish service. 44 | default: Value in the C(bf_session) fact. 45 | required: false 46 | type: dict 47 | author: 48 | - Spencer Fraint (`@sfraint `_) 49 | requirements: 50 | - "pybatfish" 51 | ''' 52 | 53 | EXAMPLES = ''' 54 | # Set the current snapshot and network names 55 | - bf_set_snapshot 56 | network: datacenter_sea 57 | snapshot: 2019-01-01 58 | ''' 59 | 60 | RETURN = ''' 61 | summary: 62 | description: Summary of action(s) performed. 63 | type: str 64 | returned: always 65 | ''' 66 | 67 | import os 68 | 69 | from ansible.module_utils.basic import AnsibleModule 70 | from ansible.module_utils.bf_util import ( 71 | create_session, get_snapshot_init_warning 72 | ) 73 | 74 | try: 75 | from pybatfish.client.session import Session 76 | except Exception as e: 77 | pybatfish_found = False 78 | else: 79 | pybatfish_found = True 80 | 81 | def run_module(): 82 | # define the available arguments/parameters that a user can pass to 83 | # the module 84 | module_args = dict( 85 | network=dict(type='str', required=True), 86 | snapshot=dict(type='str', required=True), 87 | session=dict(type='dict', required=True), 88 | ) 89 | 90 | # seed the result dict in the object 91 | # we primarily care about changed and state 92 | # change is if this module effectively modified the target 93 | # state will include any data that you want your module to pass back 94 | # for consumption, for example, in a subsequent task 95 | result = dict( 96 | changed=False, 97 | summary='', 98 | ansible_facts={}, 99 | ) 100 | 101 | # the AnsibleModule object will be our abstraction working with Ansible 102 | # this includes instantiation, a couple of common attr would be the 103 | # args/params passed to the execution, as well as if the module 104 | # supports check mode 105 | module = AnsibleModule( 106 | argument_spec=module_args, 107 | supports_check_mode=True 108 | ) 109 | 110 | if not pybatfish_found: 111 | module.fail_json(msg='Python module Pybatfish is required') 112 | 113 | if module.check_mode: 114 | return result 115 | 116 | network = module.params['network'] 117 | snapshot = module.params['snapshot'] 118 | session_params = module.params.get('session', {}) 119 | 120 | try: 121 | session = create_session(**session_params) 122 | except Exception as e: 123 | message = 'Failed to establish session with Batfish service: {}'.format(e) 124 | module.fail_json(msg=message, **result) 125 | return 126 | 127 | try: 128 | session.set_network(network) 129 | session.set_snapshot(snapshot) 130 | except Exception as e: 131 | message = 'Failed to set snapshot: {}'.format(e) 132 | module.fail_json(msg=message, **result) 133 | 134 | result['summary'] = "Snapshot set to '{}' on network '{}'".format(snapshot, network) 135 | result['result'] = { 136 | 'network': network, 137 | 'snapshot': snapshot, 138 | } 139 | result['ansible_facts']['bf_snapshot'] = snapshot 140 | result['ansible_facts']['bf_network'] = network 141 | module.exit_json(**result) 142 | 143 | def main(): 144 | run_module() 145 | 146 | if __name__ == '__main__': 147 | main() 148 | -------------------------------------------------------------------------------- /docs/bf_init_snapshot.rst: -------------------------------------------------------------------------------- 1 | .. _bf_init_snapshot: 2 | 3 | bf_init_snapshot 4 | ++++++++++++++++ 5 | Initializes a Batfish snapshot with provided snapshot data 6 | 7 | .. contents:: 8 | :local: 9 | :depth: 2 10 | 11 | 12 | Synopsis 13 | -------- 14 | 15 | 16 | * Initializes a Batfish snapshot with provided snapshot data and populates ``bf_network`` and ``bf_snapshot`` facts. 17 | 18 | 19 | 20 | Requirements 21 | ------------ 22 | The following software packages must be installed on hosts that execute this module: 23 | 24 | * pybatfish 25 | 26 | 27 | 28 | .. _module-specific-options-label: 29 | 30 | Module-specific Options 31 | ----------------------- 32 | The following options may be specified for this module: 33 | 34 | .. raw:: html 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 104 | 105 | 106 |
parametertyperequireddefaultcomments
extra_args
dictno 52 |
Additional arguments to pass to the Batfish service for snapshot initialization
53 |
network
stryes 62 |
Name of the network in which to initialize the snapshot.
63 |
overwrite
boolnoFalse 72 |
Whether to overwrite a snapshot of the same name (if it exists) in the network.
73 |
session
dictnoValue in the bf_session fact. 82 |
Batfish session object required to connect to the Batfish service.
83 |
snapshot
stryes 92 |
Name of the snapshot to initialize.
93 |
snapshot_data
stryes 102 |
Path to snapshot data directory or zip. See https://github.com/batfish/batfish/wiki/Packaging-snapshots-for-analysis for more details on packaging your snapshot for analysis.
103 |
107 |
108 | 109 | .. _bf_init_snapshot-examples-label: 110 | 111 | Examples 112 | -------- 113 | 114 | :: 115 | 116 | 117 | # Initialize a snapshot with specified snapshot data 118 | - bf_init_snapshot 119 | network: datacenter_sea 120 | snapshot: 2019-01-01 121 | snapshot_data: /path/to/snapshot/data/ 122 | # Initialize a snapshot, replacing same named snapshot if it exists 123 | - bf_init_snapshot 124 | network: network_name 125 | snapshot: duplicate_snapshot_name 126 | snapshot_data: /path/to/snapshot/data.zip 127 | overwrite: true 128 | 129 | 130 | 131 | Return Values 132 | ------------- 133 | 134 | .. raw:: html 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 187 | 188 | 189 | 190 | 191 | 194 | 195 | 196 | 197 | 198 |
namedescriptionreturnedtype
result 149 |
Information about the snapshot created.
150 |
alwayscomplex
contains: 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 181 | 182 | 183 | 184 | 185 |
namedescriptionreturnedtype
snapshot 170 |
Name of the snapshot created.
171 |
alwaysstr
network 179 |
Name of the network created.
180 |
alwaysstr
186 |
summary 192 |
Summary of action(s) performed.
193 |
alwaysstr
199 |
200 |
201 | 202 | 203 | 204 | 205 | 206 | Status 207 | ~~~~~~ 208 | 209 | This module is flagged as **preview** which means that it is not guaranteed to have a backwards compatible interface. 210 | 211 | 212 | -------------------------------------------------------------------------------- /tests/unit/test_assertions.py: -------------------------------------------------------------------------------- 1 | import six 2 | 3 | if six.PY3: 4 | from unittest.mock import patch 5 | else: 6 | from mock import patch 7 | 8 | from pandas import DataFrame 9 | from pybatfish.client.session import Session 10 | import pytest 11 | 12 | from module_utils.bf_assertion_util import get_assertion_issues, run_assertion, \ 13 | ASSERT_PASS_MESSAGE 14 | from tests.unit.test_utils import MockTableAnswer, MockQuestion 15 | 16 | 17 | @pytest.fixture 18 | def session(): 19 | yield Session(load_questions=False) 20 | 21 | 22 | def test_get_assertion_issues(session): 23 | """Confirm no issues are detected when checking valid assertion dictionary.""" 24 | assert get_assertion_issues({ 25 | 'name': 'assertion_name', 26 | 'type': 'assert_all_flows_fail', 27 | 'parameters': { 28 | 'startLocation': 'start', 29 | 'headers': 'headers', 30 | }, 31 | }, session=session) is None, 'No issues from properly formatted assertion' 32 | 33 | 34 | def test_get_assertion_issues_extra_param(session): 35 | """Confirm issue is detected when checking assertion dictionary with extra assertion parameters.""" 36 | issue = get_assertion_issues({ 37 | 'name': 'assertion_name', 38 | 'type': 'assert_all_flows_fail', 39 | 'parameters': { 40 | 'startLocation': 'start', 41 | 'headers': 'headers', 42 | 'extraParam': 'extra', 43 | }, 44 | }, session=session) 45 | # Issue message should indicate there is an invalid parameter 46 | assert 'Invalid parameter' in issue 47 | # and reference that parameter's name 48 | assert 'extraParam' in issue 49 | 50 | 51 | def test_get_assertion_issues_unsupported_assertion(session): 52 | """Confirm issue is detected when checking an unsupported assertion.""" 53 | issue = get_assertion_issues({ 54 | 'name': 'assertion_name', 55 | 'type': 'assert_that', 56 | 'parameters': { 57 | 'assertion': 'something', 58 | }, 59 | }, session=session) 60 | # Issue message should indicate this assertion isn't supported by this session 61 | assert 'does not exist in the current session' in issue 62 | # and reference that parameter's name 63 | assert 'Make sure you are establishing a session with the correct type' in issue 64 | 65 | 66 | def test_get_assertion_issues_missing_param(session): 67 | """Confirm issue is detected when checking assertion dictionary missing some mandatory parameter.""" 68 | issue = get_assertion_issues({ 69 | 'name': 'assertion_name', 70 | 'type': 'assert_all_flows_fail', 71 | 'parameters': { 72 | 'startLocation': 'start', 73 | # missing 'header' param 74 | }, 75 | }, session=session) 76 | # Issue message should indicate there is a missing parameter 77 | assert 'Missing mandatory parameter' in issue 78 | # and reference that parameter's name 79 | assert 'header' in issue 80 | 81 | 82 | def test_get_assertion_issues_no_name(session): 83 | """Confirm issue is detected when checking assertion dictionary missing a name.""" 84 | issue = get_assertion_issues({ 85 | # missing 'name' 86 | 'type': 'assert_all_flows_fail', 87 | 'parameters': { 88 | 'startLocation': 'start', 89 | 'header': 'header', 90 | }, 91 | }, session=session) 92 | # Issue message should indicate name is missing 93 | assert 'No name specified' in issue 94 | 95 | 96 | def test_get_assertion_issues_invalid_type(session): 97 | """Confirm issue is detected when checking assertion dictionary with an invalid type.""" 98 | issue = get_assertion_issues({ 99 | 'name': 'assertion_name', 100 | 'type': 'invalid_assertion_type', 101 | }, session=session) 102 | # Issue message should indicate type is missing 103 | assert 'Unknown assertion type' in issue 104 | 105 | 106 | def test_get_assertion_issues_no_type(session): 107 | """Confirm issue is detected when checking assertion dictionary missing a type.""" 108 | issue = get_assertion_issues({ 109 | 'name': 'assertion_name', 110 | # missing 'type' 111 | }, session=session) 112 | # Issue message should indicate type is missing 113 | assert 'No type specified' in issue 114 | 115 | 116 | def test_run_assertion(session): 117 | """Confirm running passing assertion results in a passing message.""" 118 | assertion = { 119 | 'name': 'assert_name', 120 | 'type': 'assert_no_undefined_references', 121 | } 122 | with patch.object(session.q, 123 | 'undefinedReferences', 124 | create=True) as mock_undef: 125 | mock_undef.return_value = MockQuestion(MockTableAnswer()) 126 | assert run_assertion(session, assertion) == ASSERT_PASS_MESSAGE 127 | 128 | 129 | def test_run_assertion_fail(session): 130 | """Confirm running failing assertion results in a message indicating failure.""" 131 | assertion = { 132 | 'name': 'assert_name', 133 | 'type': 'assert_no_undefined_references', 134 | } 135 | with patch.object(session.q, 136 | 'undefinedReferences', 137 | create=True) as mock_undef: 138 | mock_undef.return_value = MockQuestion( 139 | MockTableAnswer(DataFrame.from_records( 140 | [{'Undef': 'something'}]))) 141 | result = run_assertion(session, assertion) 142 | assert ASSERT_PASS_MESSAGE not in result 143 | assert 'Found undefined reference(s), when none were expected' in result 144 | -------------------------------------------------------------------------------- /library/bf_validate_facts.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # Copyright 2019 The Batfish Open Source Project 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | ANSIBLE_METADATA = { 17 | 'metadata_version': '1.1', 18 | 'status': ['preview'], 19 | 'supported_by': 'community' 20 | } 21 | 22 | # Note: these docs take into account the fact that the plugin handles supplying default values for some params 23 | DOCUMENTATION = ''' 24 | --- 25 | module: bf_validate_facts 26 | short_description: Validates facts for the current Batfish snapshot against the facts in the supplied directory 27 | version_added: "2.7" 28 | description: 29 | - "Validates facts for the current Batfish snapshot against the facts in the C(expected_facts) directory" 30 | options: 31 | network: 32 | description: 33 | - Name of the network to validate facts for. 34 | default: Value in the C(bf_network) fact. 35 | required: false 36 | type: str 37 | snapshot: 38 | description: 39 | - Name of the snapshot to validate facts for. 40 | default: Value in the C(bf_snapshot) fact. 41 | required: false 42 | type: str 43 | session: 44 | description: 45 | - Batfish session object required to connect to the Batfish service. 46 | default: Value in the C(bf_session) fact. 47 | required: false 48 | type: dict 49 | expected_facts: 50 | description: 51 | - Directory to read expected facts from. 52 | required: true 53 | type: str 54 | author: 55 | - Spencer Fraint (`@sfraint `_) 56 | requirements: 57 | - "pybatfish" 58 | ''' 59 | 60 | EXAMPLES = ''' 61 | # Validate current snapshot facts against local YAML facts 62 | - bf_validate_facts: 63 | expected_facts: /path/to/local/YAML/files/ 64 | ''' 65 | 66 | RETURN = ''' 67 | summary: 68 | description: Summary of action(s) performed. 69 | type: str 70 | returned: always 71 | result: 72 | description: Contains a map of node-name to dictionary of failures for that node. 73 | returned: when validation does not pass 74 | type: dict 75 | ''' 76 | 77 | from ansible.module_utils.basic import AnsibleModule 78 | from ansible.module_utils.bf_util import (create_session, 79 | set_snapshot) 80 | 81 | try: 82 | from pybatfish.client.session import Session 83 | except Exception as e: 84 | pybatfish_found = False 85 | else: 86 | pybatfish_found = True 87 | 88 | 89 | def run_module(): 90 | # define the available arguments/parameters that a user can pass to 91 | # the module 92 | module_args = dict( 93 | network=dict(type='str', required=True), 94 | snapshot=dict(type='str', required=True), 95 | expected_facts=dict(type='str', required=True), 96 | session=dict(type='dict', required=True), 97 | ) 98 | 99 | # seed the result dict in the object 100 | # we primarily care about changed and state 101 | # change is if this module effectively modified the target 102 | # state will include any data that you want your module to pass back 103 | # for consumption, for example, in a subsequent task 104 | result = dict( 105 | changed=False, 106 | result='', 107 | summary='', 108 | ) 109 | 110 | # the AnsibleModule object will be our abstraction working with Ansible 111 | # this includes instantiation, a couple of common attr would be the 112 | # args/params passed to the execution, as well as if the module 113 | # supports check mode 114 | module = AnsibleModule( 115 | argument_spec=module_args, 116 | supports_check_mode=True 117 | ) 118 | 119 | if not pybatfish_found: 120 | module.fail_json(msg='Python module Pybatfish is required') 121 | 122 | if module.check_mode: 123 | return result 124 | 125 | input_directory = module.params['expected_facts'] 126 | session_params = module.params.get('session', {}) 127 | network = module.params.get('network') 128 | snapshot = module.params.get('snapshot') 129 | 130 | try: 131 | session = create_session(**session_params) 132 | except Exception as e: 133 | message = 'Failed to establish session with Batfish service: {}'.format( 134 | e) 135 | module.fail_json(msg=message, **result) 136 | return 137 | 138 | try: 139 | set_snapshot(session=session, network=network, snapshot=snapshot) 140 | except Exception as e: 141 | message = 'Failed to set snapshot: {}'.format(e) 142 | module.fail_json(msg=message, **result) 143 | return 144 | 145 | try: 146 | failures = session.validate_facts(expected_facts=input_directory) 147 | except Exception as e: 148 | message = 'Failed to validate facts: {}'.format(e) 149 | module.fail_json(msg=message, **result) 150 | return 151 | 152 | summary = 'Actual facts match expected facts' 153 | if failures: 154 | summary = 'Validation failed for the following nodes: {}.'.format( 155 | list(failures.keys())) 156 | 157 | # Overall status of command execution 158 | result['summary'] = summary 159 | result['result'] = failures 160 | # Indicate failure to Ansible in the case of failed validation 161 | if failures: 162 | module.fail_json(msg=summary, **result) 163 | 164 | module.exit_json(**result) 165 | 166 | 167 | def main(): 168 | run_module() 169 | 170 | 171 | if __name__ == '__main__': 172 | main() 173 | -------------------------------------------------------------------------------- /library/bf_session.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # Copyright 2019 The Batfish Open Source Project 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | ANSIBLE_METADATA = { 17 | 'metadata_version': '1.1', 18 | 'status': ['preview'], 19 | 'supported_by': 'community' 20 | } 21 | 22 | DOCUMENTATION = ''' 23 | --- 24 | module: bf_session 25 | short_description: Builds a session for use with other Batfish Ansible modules 26 | version_added: "2.7" 27 | description: 28 | - "Builds a session for use with other Batfish Ansible modules and populates C(bf_session) fact." 29 | options: 30 | host: 31 | description: 32 | - Host (resolvable name or IP address) running the Batfish service. 33 | required: true 34 | type: str 35 | name: 36 | default: '"default"' 37 | description: 38 | - Name of the session. 39 | required: false 40 | type: str 41 | parameters: 42 | default: None 43 | description: 44 | - 'Dictionary with additional parameters used to configure the session. Use C({ssl: true}) to use SSL.' 45 | required: false 46 | type: dict 47 | author: 48 | - Spencer Fraint (`@sfraint `_) 49 | requirements: 50 | - "pybatfish" 51 | ''' 52 | 53 | EXAMPLES = ''' 54 | # Establish session with Batfish service running on localhost 55 | - bf_session: 56 | host: localhost 57 | name: my_session 58 | # Establish SSL session with Batfish service running at 10.10.10.10 59 | - bf_session: 60 | host: 10.10.10.10 61 | name: my_session 62 | parameters: 63 | ssl: true 64 | # Establish SSL session with Batfish Enterprise service running at 10.10.10.10 65 | - bf_session: 66 | host: 10.10.10.10 67 | name: enterprise_session 68 | parameters: 69 | ssl: true 70 | session_type: bfe 71 | ''' 72 | 73 | RETURN = ''' 74 | summary: 75 | description: Summary of action(s) performed. 76 | type: str 77 | returned: always 78 | session: 79 | description: Details about the created session. 80 | type: complex 81 | contains: 82 | host: 83 | description: Host where service is hosted 84 | type: str 85 | returned: always 86 | parameters: 87 | description: Additional parameters to connect to the service 88 | type: dict 89 | returned: if supplied by user 90 | returned: always 91 | ''' 92 | 93 | import time 94 | from datetime import datetime 95 | 96 | from ansible.module_utils.basic import AnsibleModule 97 | from ansible.module_utils.bf_util import create_session 98 | 99 | try: 100 | from pybatfish.client.session import Session 101 | except Exception as e: 102 | pybatfish_found = False 103 | else: 104 | pybatfish_found = True 105 | 106 | # Constants for session creation retry 107 | _MAX_RETRY_TIME = 10 108 | _RETRY_DELAY = 3 109 | 110 | 111 | def run_module(): 112 | # define the available arguments/parameters that a user can pass to 113 | # the module 114 | module_args = dict( 115 | host=dict(type='str', required=True), 116 | name=dict(type='str', required=False, default='default'), 117 | parameters=dict(type='dict', required=False), 118 | ) 119 | 120 | # seed the result dict in the object 121 | # we primarily care about changed and state 122 | # change is if this module effectively modified the target 123 | # state will include any data that you want your module to pass back 124 | # for consumption, for example, in a subsequent task 125 | result = dict( 126 | changed=False, 127 | session='', 128 | summary='', 129 | ansible_facts={}, 130 | ) 131 | 132 | # the AnsibleModule object will be our abstraction working with Ansible 133 | # this includes instantiation, a couple of common attr would be the 134 | # args/params passed to the execution, as well as if the module 135 | # supports check mode 136 | module = AnsibleModule( 137 | argument_spec=module_args, 138 | supports_check_mode=True 139 | ) 140 | 141 | if not pybatfish_found: 142 | module.fail_json(msg='Python module Pybatfish is required') 143 | 144 | if module.check_mode: 145 | return result 146 | 147 | host = module.params['host'] 148 | name = module.params['name'] 149 | parameters = module.params['parameters'] 150 | 151 | if parameters is None: 152 | parameters = {} 153 | parameters['host'] = host 154 | 155 | # Allow a few retries in case the service isn't ready yet 156 | retry_time = 0 157 | while True: 158 | try_start_time = datetime.now() 159 | try: 160 | create_session(**parameters) 161 | break 162 | except Exception as session_e: 163 | if retry_time < _MAX_RETRY_TIME: 164 | try_time = (datetime.now() - try_start_time).seconds 165 | sleep_time = max(_RETRY_DELAY - try_time, 166 | 1) # sleep at least 1 second 167 | time.sleep(sleep_time) 168 | retry_time += try_time + sleep_time 169 | else: 170 | message = 'Failed to establish session with Batfish service: {}'.format( 171 | session_e) 172 | module.fail_json(msg=message, **result) 173 | 174 | # Overall status of command execution 175 | result['summary'] = "Session established to '{}' ({})".format(host, name) 176 | result['session'] = parameters 177 | result['changed'] = True 178 | result['ansible_facts']['bf_session'] = parameters 179 | module.exit_json(**result) 180 | 181 | 182 | def main(): 183 | run_module() 184 | 185 | 186 | if __name__ == '__main__': 187 | main() 188 | -------------------------------------------------------------------------------- /docs/assertions2rst.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # (c) 2012, Jan-Piet Mens 3 | # 4 | # This file is part of Ansible 5 | # 6 | # Modified to support stand-alone Galaxy documentation 7 | # Copyright (c) 2014, 2017-2018 Juniper Networks Inc. 8 | # 2014, Rick Sherman 9 | # 10 | # Modified to support stand-alone Galaxy documentation for Batfish 11 | # Copyright (c) 2019 Intentionet, Inc. 12 | # 13 | # Ansible is free software: you can redistribute it and/or modify 14 | # it under the terms of the GNU General Public License as published by 15 | # the Free Software Foundation, either version 3 of the License, or 16 | # (at your option) any later version. 17 | # 18 | # Ansible is distributed in the hope that it will be useful, 19 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | # GNU General Public License for more details. 22 | # 23 | # You should have received a copy of the GNU General Public License 24 | # along with Ansible. If not, see . 25 | # 26 | 27 | import ast 28 | import os 29 | import datetime 30 | from six import print_ 31 | 32 | from ansible.module_utils.six import iteritems 33 | from ansible.parsing.yaml.loader import AnsibleLoader 34 | from ansible.utils.display import Display 35 | 36 | from common import jinja2_environment 37 | 38 | ASSERTIONSFILE = "../module_utils/bf_assertion_util.py" 39 | OUTPUTDIR = "./" 40 | 41 | display = Display() 42 | 43 | 44 | # this function is modified from the one in plugin_docs.py 45 | def read_assertion_docs(filename, verbose=True, ignore_errors=True): 46 | """ 47 | Search for assignment of ASSERTIONS variables in the given file. 48 | Parse DOCUMENTATION from YAML and return the YAML doc or None together with EXAMPLES, as plain text. 49 | """ 50 | 51 | data = { 52 | 'assertions': None, 53 | } 54 | 55 | string_to_vars = { 56 | 'ASSERTIONS': 'assertions', 57 | } 58 | 59 | try: 60 | with open(filename, 'rb') as b_module_data: 61 | M = ast.parse(b_module_data.read()) 62 | 63 | for child in M.body: 64 | if isinstance(child, ast.Assign): 65 | for t in child.targets: 66 | try: 67 | theid = t.id 68 | except AttributeError: 69 | # skip errors can happen when trying to use the normal code 70 | display.warning("Failed to assign id for %s on %s, skipping" % (t, filename)) 71 | continue 72 | 73 | if theid in string_to_vars: 74 | varkey = string_to_vars[theid] 75 | if isinstance(child.value, ast.Dict): 76 | data[varkey] = ast.literal_eval(child.value) 77 | else: 78 | if theid == 'ASSERTIONS': 79 | # string should be yaml 80 | data[varkey] = AnsibleLoader(child.value.s, file_name=filename).get_single_data() 81 | else: 82 | # not yaml, should be a simple string 83 | data[varkey] = to_text(child.value.s) 84 | display.debug('assigned :%s' % varkey) 85 | 86 | except Exception: 87 | if verbose: 88 | display.error("unable to parse %s" % filename) 89 | if not ignore_errors: 90 | raise 91 | 92 | return data 93 | 94 | 95 | def process_assertion(assertion_name, assertion_dict, template, out_file): 96 | print_("Processing assertion %s" % assertion_name) 97 | 98 | # add name as it does not appear in the dictionary by itself 99 | assertion_dict['assertion_name'] = assertion_name 100 | 101 | required_fields = ('short_description',) 102 | for field in required_fields: 103 | if field not in assertion_dict: 104 | print_("%s: WARNING: ASSERTION MISSING field '%s'" % (assertion_name, field)) 105 | 106 | not_nullable_fields = ('short_description',) 107 | for field in not_nullable_fields: 108 | if field in assertion_dict and assertion_dict[field] in (None, ''): 109 | print_("%s: WARNING: ASSERTION field '%s' is null/empty value=%s" % ( 110 | assertion_name, field, assertion_dict[field])) 111 | 112 | # 113 | # The present template gets everything from doc so we spend most of this 114 | # function moving data into doc for the template to reference 115 | # 116 | 117 | option_names = [] 118 | if 'options' in assertion_dict and assertion_dict['options']: 119 | for (k, v) in iteritems(assertion_dict['options']): 120 | # Error out if there's no description 121 | if 'description' not in assertion_dict['options'][k]: 122 | raise AnsibleError("Missing required description for option %s in %s " % (k, assertion_name)) 123 | 124 | # Error out if required isn't a boolean (people have been putting 125 | # information on when something is required in here. Those need 126 | # to go in the description instead). 127 | required_value = assertion_dict['options'][k].get('required', False) 128 | if not isinstance(required_value, bool): 129 | raise AnsibleError("Invalid required value '%s' for option '%s' in '%s' (must be truthy)" % ( 130 | required_value, k, assertion_name)) 131 | 132 | # Make sure description is a list of lines for later formatting 133 | if not isinstance(assertion_dict['options'][k]['description'], list): 134 | assertion_dict['options'][k]['description'] = [assertion_dict['options'][k]['description']] 135 | option_names.append(k) 136 | option_names.sort() 137 | assertion_dict['option_keys'] = option_names 138 | 139 | assertion_dict['now_date'] = datetime.date.today().strftime('%Y-%m-%d') 140 | 141 | # here is where we build the table of contents... 142 | text = template.render(assertion_dict) 143 | print(text, file=out_file) 144 | 145 | 146 | def main(): 147 | env, template, _ = jinja2_environment('.', 'rst', "assertion.j2") 148 | assertions = read_assertion_docs(ASSERTIONSFILE) 149 | 150 | out_file_path = os.path.join(OUTPUTDIR, "assertions.rst") 151 | out_file = open(out_file_path, "w") 152 | out_file.write('Assertions supported by bf_assert module\n') 153 | out_file.write('++++++++++++++++++++++++++++++++++++++++\n') 154 | out_file.write('\n') 155 | 156 | out_file.write(".. contents::\n") 157 | out_file.write(" :local:\n") 158 | out_file.write(" :depth: 2\n") 159 | out_file.write('\n') 160 | 161 | 162 | for assertion_name in assertions["assertions"]: 163 | process_assertion(assertion_name, assertions["assertions"][assertion_name], template, out_file) 164 | 165 | out_file.close() 166 | 167 | 168 | if __name__ == '__main__': 169 | main() 170 | -------------------------------------------------------------------------------- /library/bf_extract_facts.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # Copyright 2019 The Batfish Open Source Project 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | ANSIBLE_METADATA = { 17 | 'metadata_version': '1.1', 18 | 'status': ['preview'], 19 | 'supported_by': 'community' 20 | } 21 | 22 | # Note: these docs take into account the fact that the plugin handles supplying default values for some params 23 | DOCUMENTATION = ''' 24 | --- 25 | module: bf_extract_facts 26 | short_description: Extracts facts for a Batfish snapshot 27 | version_added: "2.7" 28 | description: 29 | - "Extracts and returns facts for a Batfish snapshot and saves them (one YAML file node) to the output directory if specified." 30 | options: 31 | nodes: 32 | description: 33 | - Nodes to extract facts for. See U(https://github.com/batfish/batfish/blob/master/questions/Parameters.md#node-specifier) for more details on node specifiers. 34 | required: false 35 | type: str 36 | default: All nodes 37 | network: 38 | description: 39 | - Name of the network to extract facts for. 40 | default: Value in the C(bf_network) fact. 41 | required: false 42 | type: str 43 | snapshot: 44 | description: 45 | - Name of the snapshot to extract facts for. 46 | default: Value in the C(bf_snapshot) fact. 47 | required: false 48 | type: str 49 | output_directory: 50 | default: None 51 | description: 52 | - Directory to save facts to. 53 | required: false 54 | type: str 55 | session: 56 | description: 57 | - Batfish session object required to connect to the Batfish service. 58 | default: Value in the C(bf_session) fact. 59 | required: false 60 | type: dict 61 | author: 62 | - Spencer Fraint (`@sfraint `_) 63 | requirements: 64 | - "pybatfish" 65 | ''' 66 | 67 | EXAMPLES = ''' 68 | # Extract facts and save to an output directory 69 | - bf_extract_facts: 70 | output_directory: output/facts/ 71 | # Extract facts for nodes whose names contain as1border or host 72 | - bf_extract_facts: 73 | nodes: /as1border|host/ 74 | ''' 75 | 76 | RETURN = ''' 77 | summary: 78 | description: Summary of action(s) performed. 79 | type: str 80 | returned: always 81 | result: 82 | description: Dictionary of extracted facts. 83 | type: complex 84 | contains: 85 | nodes: 86 | description: Dictionary of node-name to node-facts for each node. 87 | type: complex 88 | returned: always 89 | version: 90 | description: Fact-format version of the returned facts. 91 | type: str 92 | returned: always 93 | returned: always 94 | ''' 95 | 96 | from ansible.module_utils.basic import AnsibleModule 97 | from ansible.module_utils.bf_util import (create_session, get_node_count, 98 | set_snapshot, 99 | NODE_SPECIFIER_INSTRUCTIONS_URL) 100 | 101 | try: 102 | from pybatfish.client.session import Session 103 | except Exception as e: 104 | pybatfish_found = False 105 | else: 106 | pybatfish_found = True 107 | 108 | 109 | def run_module(): 110 | # define the available arguments/parameters that a user can pass to 111 | # the module 112 | module_args = dict( 113 | nodes=dict(type='str', required=False, default='.*'), 114 | network=dict(type='str', required=True), 115 | snapshot=dict(type='str', required=True), 116 | output_directory=dict(type='str', required=False), 117 | session=dict(type='dict', required=True), 118 | ) 119 | 120 | # seed the result dict in the object 121 | # we primarily care about changed and state 122 | # change is if this module effectively modified the target 123 | # state will include any data that you want your module to pass back 124 | # for consumption, for example, in a subsequent task 125 | result = dict( 126 | changed=False, 127 | result='', 128 | summary='', 129 | ) 130 | 131 | # the AnsibleModule object will be our abstraction working with Ansible 132 | # this includes instantiation, a couple of common attr would be the 133 | # args/params passed to the execution, as well as if the module 134 | # supports check mode 135 | module = AnsibleModule( 136 | argument_spec=module_args, 137 | supports_check_mode=True 138 | ) 139 | 140 | if not pybatfish_found: 141 | module.fail_json(msg='Python module Pybatfish is required') 142 | 143 | if module.check_mode: 144 | return result 145 | 146 | output_directory = module.params['output_directory'] 147 | nodes = module.params['nodes'] 148 | session_params = module.params.get('session', {}) 149 | network = module.params.get('network') 150 | snapshot = module.params.get('snapshot') 151 | 152 | try: 153 | session = create_session(**session_params) 154 | except Exception as e: 155 | message = 'Failed to establish session with Batfish service: {}'.format( 156 | e) 157 | module.fail_json(msg=message, **result) 158 | return 159 | 160 | try: 161 | set_snapshot(session=session, network=network, snapshot=snapshot) 162 | except Exception as e: 163 | message = 'Failed to set snapshot: {}'.format(e) 164 | module.fail_json(msg=message, **result) 165 | return 166 | 167 | try: 168 | facts = session.extract_facts(nodes=nodes, 169 | output_directory=output_directory) 170 | if not get_node_count(facts): 171 | result['warnings'] = [ 172 | 'No nodes found matching node specifier "{}". See here for details on how to use node specifiers: {}'.format( 173 | nodes, NODE_SPECIFIER_INSTRUCTIONS_URL)] 174 | except Exception as e: 175 | message = 'Failed to extract facts: {}'.format(e) 176 | module.fail_json(msg=message, **result) 177 | return 178 | 179 | summary = "Got facts for nodes: '{}'".format(nodes) 180 | if output_directory: 181 | summary += ', wrote facts to directory: {}'.format(output_directory) 182 | 183 | # Overall status of command execution 184 | result['summary'] = summary 185 | result['result'] = facts 186 | module.exit_json(**result) 187 | 188 | 189 | def main(): 190 | run_module() 191 | 192 | 193 | if __name__ == '__main__': 194 | main() 195 | -------------------------------------------------------------------------------- /tutorials/playbooks/data/validation/as1core1.yml: -------------------------------------------------------------------------------- 1 | nodes: 2 | as1core1: 3 | AS_Path_Access_Lists: [] 4 | Authentication_Key_Chains: [] 5 | BGP: 6 | Multipath_EBGP: true 7 | Multipath_IBGP: true 8 | Multipath_Match_Mode: EXACT_PATH 9 | Neighbors: 10 | 1.1.1.1: 11 | Cluster_ID: 1.10.1.1 12 | Export_Policy: [] 13 | Import_Policy: [] 14 | Is_Passive: false 15 | Local_AS: 1 16 | Local_IP: 1.10.1.1 17 | Local_Interface: null 18 | Peer_Group: as1 19 | Remote_AS: '1' 20 | Remote_IP: 1.1.1.1 21 | Route_Reflector_Client: true 22 | Send_Community: true 23 | VRF: default 24 | 1.2.2.2: 25 | Cluster_ID: 1.10.1.1 26 | Export_Policy: [] 27 | Import_Policy: [] 28 | Is_Passive: false 29 | Local_AS: 1 30 | Local_IP: 1.10.1.1 31 | Local_Interface: null 32 | Peer_Group: as1 33 | Remote_AS: '1' 34 | Remote_IP: 1.2.2.2 35 | Route_Reflector_Client: true 36 | Send_Community: true 37 | VRF: default 38 | Route_Reflector: true 39 | Router_ID: 1.10.1.1 40 | Tie_Breaker: ARRIVAL_ORDER 41 | VRF: default 42 | Configuration_Format: CISCO_IOS 43 | DNS: 44 | DNS_Servers: [] 45 | DNS_Source_Interface: null 46 | Default_Cross_Zone_Action: PERMIT 47 | Default_Inbound_Action: PERMIT 48 | Domain_Name: lab.local 49 | Hostname: as1core1 50 | IP6_Access_Lists: [] 51 | IP_Access_Lists: [] 52 | IPsec: 53 | IKE_Phase1_Keys: [] 54 | IKE_Phase1_Policies: [] 55 | IKE_Phase1_Proposals: [] 56 | IPsec_Peer_Configs: [] 57 | IPsec_Phase2_Policies: [] 58 | IPsec_Phase2_Proposals: [] 59 | Interfaces: 60 | Ethernet0/0: 61 | Access_VLAN: null 62 | Active: false 63 | All_Prefixes: [] 64 | Allowed_VLANs: '' 65 | Auto_State_VLAN: true 66 | Bandwidth: 10000000.0 67 | Blacklisted: false 68 | Channel_Group: null 69 | Channel_Group_Members: [] 70 | DHCP_Relay_Addresses: [] 71 | Declared_Names: 72 | - Ethernet0/0 73 | Description: null 74 | Encapsulation_VLAN: null 75 | HSRP_Groups: [] 76 | HSRP_Version: null 77 | Incoming_Filter_Name: null 78 | MLAG_ID: null 79 | MTU: 1500 80 | Native_VLAN: null 81 | Outgoing_Filter_Name: null 82 | PBR_Policy_Name: null 83 | Primary_Address: null 84 | Primary_Network: null 85 | Proxy_ARP: true 86 | Rip_Enabled: false 87 | Rip_Passive: false 88 | Spanning_Tree_Portfast: false 89 | Speed: 10000000.0 90 | Switchport: false 91 | Switchport_Mode: NONE 92 | Switchport_Trunk_Encapsulation: DOT1Q 93 | VRF: default 94 | VRRP_Groups: [] 95 | Zone_Name: null 96 | GigabitEthernet0/0: 97 | Access_VLAN: null 98 | Active: true 99 | All_Prefixes: 100 | - 1.0.2.2/24 101 | Allowed_VLANs: '' 102 | Auto_State_VLAN: true 103 | Bandwidth: 1000000000.0 104 | Blacklisted: false 105 | Channel_Group: null 106 | Channel_Group_Members: [] 107 | DHCP_Relay_Addresses: [] 108 | Declared_Names: 109 | - GigabitEthernet0/0 110 | Description: null 111 | Encapsulation_VLAN: null 112 | HSRP_Groups: [] 113 | HSRP_Version: null 114 | Incoming_Filter_Name: null 115 | MLAG_ID: null 116 | MTU: 1500 117 | Native_VLAN: null 118 | Outgoing_Filter_Name: null 119 | PBR_Policy_Name: null 120 | Primary_Address: 1.0.2.2/24 121 | Primary_Network: 1.0.2.0/24 122 | Proxy_ARP: true 123 | Rip_Enabled: false 124 | Rip_Passive: false 125 | Spanning_Tree_Portfast: false 126 | Speed: 1000000000.0 127 | Switchport: false 128 | Switchport_Mode: NONE 129 | Switchport_Trunk_Encapsulation: DOT1Q 130 | VRF: default 131 | VRRP_Groups: [] 132 | Zone_Name: null 133 | GigabitEthernet1/0: 134 | Access_VLAN: null 135 | Active: true 136 | All_Prefixes: 137 | - 1.0.1.2/24 138 | Allowed_VLANs: '' 139 | Auto_State_VLAN: true 140 | Bandwidth: 1000000000.0 141 | Blacklisted: false 142 | Channel_Group: null 143 | Channel_Group_Members: [] 144 | DHCP_Relay_Addresses: [] 145 | Declared_Names: 146 | - GigabitEthernet1/0 147 | Description: null 148 | Encapsulation_VLAN: null 149 | HSRP_Groups: [] 150 | HSRP_Version: null 151 | Incoming_Filter_Name: null 152 | MLAG_ID: null 153 | MTU: 1500 154 | Native_VLAN: null 155 | Outgoing_Filter_Name: null 156 | PBR_Policy_Name: null 157 | Primary_Address: 1.0.1.2/24 158 | Primary_Network: 1.0.1.0/24 159 | Proxy_ARP: true 160 | Rip_Enabled: false 161 | Rip_Passive: false 162 | Spanning_Tree_Portfast: false 163 | Speed: 1000000000.0 164 | Switchport: false 165 | Switchport_Mode: NONE 166 | Switchport_Trunk_Encapsulation: DOT1Q 167 | VRF: default 168 | VRRP_Groups: [] 169 | Zone_Name: null 170 | Loopback0: 171 | Access_VLAN: null 172 | Active: true 173 | All_Prefixes: 174 | - 1.10.1.1/32 175 | Allowed_VLANs: '' 176 | Auto_State_VLAN: true 177 | Bandwidth: 8000000000.0 178 | Blacklisted: false 179 | Channel_Group: null 180 | Channel_Group_Members: [] 181 | DHCP_Relay_Addresses: [] 182 | Declared_Names: 183 | - Loopback0 184 | Description: null 185 | Encapsulation_VLAN: null 186 | HSRP_Groups: [] 187 | HSRP_Version: null 188 | Incoming_Filter_Name: null 189 | MLAG_ID: null 190 | MTU: 1500 191 | Native_VLAN: null 192 | Outgoing_Filter_Name: null 193 | PBR_Policy_Name: null 194 | Primary_Address: 1.10.1.1/32 195 | Primary_Network: 1.10.1.1/32 196 | Proxy_ARP: true 197 | Rip_Enabled: false 198 | Rip_Passive: false 199 | Spanning_Tree_Portfast: false 200 | Speed: null 201 | Switchport: false 202 | Switchport_Mode: NONE 203 | Switchport_Trunk_Encapsulation: DOT1Q 204 | VRF: default 205 | VRRP_Groups: [] 206 | Zone_Name: null 207 | NTP: 208 | NTP_Servers: [] 209 | NTP_Source_Interface: null 210 | PBR_Policies: [] 211 | Route6_Filter_Lists: [] 212 | Route_Filter_Lists: [] 213 | Routing_Policies: [] 214 | SNMP: 215 | SNMP_Source_Interface: null 216 | SNMP_Trap_Servers: [] 217 | Syslog: 218 | Logging_Servers: 219 | - 1.1.1.1 220 | - 2.2.2.2 221 | Logging_Source_Interface: null 222 | TACACS: 223 | TACACS_Servers: [] 224 | TACACS_Source_Interface: null 225 | VRFs: 226 | - default 227 | Zones: [] 228 | version: batfish_v0 229 | -------------------------------------------------------------------------------- /library/bf_init_snapshot.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # Copyright 2019 The Batfish Open Source Project 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | ANSIBLE_METADATA = { 17 | 'metadata_version': '1.1', 18 | 'status': ['preview'], 19 | 'supported_by': 'community' 20 | } 21 | 22 | # Note: these docs take into account the fact that the plugin handles supplying default values for some params 23 | DOCUMENTATION = ''' 24 | --- 25 | module: bf_init_snapshot 26 | short_description: Initializes a Batfish snapshot with provided snapshot data 27 | version_added: "2.7" 28 | description: 29 | - "Initializes a Batfish snapshot with provided snapshot data and populates C(bf_network) and C(bf_snapshot) facts." 30 | options: 31 | network: 32 | description: 33 | - Name of the network in which to initialize the snapshot. 34 | required: true 35 | type: str 36 | snapshot: 37 | description: 38 | - Name of the snapshot to initialize. 39 | required: true 40 | type: str 41 | snapshot_data: 42 | description: 43 | - Path to snapshot data directory or zip. See U(https://github.com/batfish/batfish/wiki/Packaging-snapshots-for-analysis) for more details on packaging your snapshot for analysis. 44 | required: true 45 | type: str 46 | overwrite: 47 | default: false 48 | description: 49 | - Whether to overwrite a snapshot of the same name (if it exists) in the network. 50 | required: false 51 | type: bool 52 | session: 53 | description: 54 | - Batfish session object required to connect to the Batfish service. 55 | default: Value in the C(bf_session) fact. 56 | required: false 57 | type: dict 58 | extra_args: 59 | description: 60 | - Additional arguments to pass to the Batfish service for snapshot initialization 61 | required: false 62 | type: dict 63 | author: 64 | - Spencer Fraint (`@sfraint `_) 65 | requirements: 66 | - "pybatfish" 67 | ''' 68 | 69 | EXAMPLES = ''' 70 | # Initialize a snapshot with specified snapshot data 71 | - bf_init_snapshot 72 | network: datacenter_sea 73 | snapshot: 2019-01-01 74 | snapshot_data: /path/to/snapshot/data/ 75 | # Initialize a snapshot, replacing same named snapshot if it exists 76 | - bf_init_snapshot 77 | network: network_name 78 | snapshot: duplicate_snapshot_name 79 | snapshot_data: /path/to/snapshot/data.zip 80 | overwrite: true 81 | ''' 82 | 83 | RETURN = ''' 84 | summary: 85 | description: Summary of action(s) performed. 86 | type: str 87 | returned: always 88 | result: 89 | description: Information about the snapshot created. 90 | type: complex 91 | contains: 92 | snapshot: 93 | description: Name of the snapshot created. 94 | type: str 95 | returned: always 96 | network: 97 | description: Name of the network created. 98 | type: str 99 | returned: always 100 | returned: always 101 | ''' 102 | 103 | from ansible.module_utils.basic import AnsibleModule 104 | from ansible.module_utils.bf_util import ( 105 | create_session, get_snapshot_init_warning 106 | ) 107 | 108 | try: 109 | from pybatfish.client.session import Session 110 | except Exception as e: 111 | pybatfish_found = False 112 | else: 113 | pybatfish_found = True 114 | 115 | def run_module(): 116 | # define the available arguments/parameters that a user can pass to 117 | # the module 118 | module_args = dict( 119 | network=dict(type='str', required=True), 120 | snapshot=dict(type='str', required=True), 121 | snapshot_data=dict(type='str', required=True), 122 | overwrite=dict(type='bool', required=False, default=False), 123 | session=dict(type='dict', required=True), 124 | extra_args=dict(type='dict', required=False) 125 | ) 126 | 127 | # seed the result dict in the object 128 | # we primarily care about changed and state 129 | # change is if this module effectively modified the target 130 | # state will include any data that you want your module to pass back 131 | # for consumption, for example, in a subsequent task 132 | result = dict( 133 | changed=False, 134 | result='', 135 | summary='', 136 | ansible_facts={}, 137 | ) 138 | 139 | # the AnsibleModule object will be our abstraction working with Ansible 140 | # this includes instantiation, a couple of common attr would be the 141 | # args/params passed to the execution, as well as if the module 142 | # supports check mode 143 | module = AnsibleModule( 144 | argument_spec=module_args, 145 | supports_check_mode=True 146 | ) 147 | 148 | if not pybatfish_found: 149 | module.fail_json(msg='Python module Pybatfish is required') 150 | 151 | if module.check_mode: 152 | return result 153 | 154 | network = module.params['network'] 155 | snapshot = module.params['snapshot'] 156 | snapshot_data = module.params['snapshot_data'] 157 | overwrite = module.params['overwrite'] 158 | session_params = module.params.get('session', {}) 159 | extra_args = module.params.get('extra_args', {}) 160 | 161 | try: 162 | session = create_session(**session_params) 163 | except Exception as e: 164 | message = 'Failed to establish session with Batfish service: {}'.format(e) 165 | module.fail_json(msg=message, **result) 166 | return 167 | 168 | session.set_network(network) 169 | 170 | try: 171 | session.init_snapshot(snapshot_data, snapshot, overwrite=overwrite, extra_args=extra_args) 172 | except Exception as e: 173 | message = 'Failed to initialize snapshot: {}'.format(e) 174 | module.fail_json(msg=message, **result) 175 | 176 | try: 177 | warn = get_snapshot_init_warning(session) 178 | if warn: 179 | result['warnings'] = [warn] 180 | except Exception as e: 181 | result['warnings'] = 'Failed to check snapshot init status: {}'.format(e) 182 | 183 | # Overall status of command execution 184 | result['summary'] = "Snapshot '{}' created in network '{}'".format(snapshot, network) 185 | result['result'] = { 186 | 'network': network, 187 | 'snapshot': snapshot, 188 | } 189 | result['changed'] = True 190 | result['ansible_facts']['bf_snapshot'] = snapshot 191 | result['ansible_facts']['bf_network'] = network 192 | 193 | module.exit_json(**result) 194 | 195 | def main(): 196 | run_module() 197 | 198 | if __name__ == '__main__': 199 | main() 200 | -------------------------------------------------------------------------------- /tutorials/playbooks/data/validation/as3border2.yml: -------------------------------------------------------------------------------- 1 | nodes: 2 | as3border2: 3 | AS_Path_Access_Lists: [] 4 | Authentication_Key_Chains: [] 5 | BGP: 6 | Multipath_EBGP: true 7 | Multipath_IBGP: true 8 | Multipath_Match_Mode: EXACT_PATH 9 | Neighbors: 10 | 10.13.22.1: 11 | Cluster_ID: null 12 | Export_Policy: 13 | - as3_to_as1 14 | Import_Policy: 15 | - as1_to_as3 16 | Is_Passive: false 17 | Local_AS: 3 18 | Local_IP: 10.13.22.3 19 | Local_Interface: null 20 | Peer_Group: as1 21 | Remote_AS: '1' 22 | Remote_IP: 10.13.22.1 23 | Route_Reflector_Client: false 24 | Send_Community: true 25 | VRF: default 26 | 3.10.1.1: 27 | Cluster_ID: null 28 | Export_Policy: [] 29 | Import_Policy: [] 30 | Is_Passive: false 31 | Local_AS: 3 32 | Local_IP: 3.2.2.2 33 | Local_Interface: null 34 | Peer_Group: as3 35 | Remote_AS: '3' 36 | Remote_IP: 3.10.1.1 37 | Route_Reflector_Client: false 38 | Send_Community: true 39 | VRF: default 40 | Route_Reflector: false 41 | Router_ID: 3.2.2.2 42 | Tie_Breaker: ARRIVAL_ORDER 43 | VRF: default 44 | Configuration_Format: CISCO_IOS 45 | DNS: 46 | DNS_Servers: [] 47 | DNS_Source_Interface: null 48 | Default_Cross_Zone_Action: PERMIT 49 | Default_Inbound_Action: PERMIT 50 | Domain_Name: lab.local 51 | Hostname: as3border2 52 | IP6_Access_Lists: [] 53 | IP_Access_Lists: 54 | - '101' 55 | - '102' 56 | - '103' 57 | IPsec: 58 | IKE_Phase1_Keys: [] 59 | IKE_Phase1_Policies: [] 60 | IKE_Phase1_Proposals: [] 61 | IPsec_Peer_Configs: [] 62 | IPsec_Phase2_Policies: [] 63 | IPsec_Phase2_Proposals: [] 64 | Interfaces: 65 | Ethernet0/0: 66 | Access_VLAN: null 67 | Active: false 68 | All_Prefixes: [] 69 | Allowed_VLANs: '' 70 | Auto_State_VLAN: true 71 | Bandwidth: 10000000.0 72 | Blacklisted: false 73 | Channel_Group: null 74 | Channel_Group_Members: [] 75 | DHCP_Relay_Addresses: [] 76 | Declared_Names: 77 | - Ethernet0/0 78 | Description: null 79 | Encapsulation_VLAN: null 80 | HSRP_Groups: [] 81 | HSRP_Version: null 82 | Incoming_Filter_Name: null 83 | MLAG_ID: null 84 | MTU: 1500 85 | Native_VLAN: null 86 | Outgoing_Filter_Name: null 87 | PBR_Policy_Name: null 88 | Primary_Address: null 89 | Primary_Network: null 90 | Proxy_ARP: true 91 | Rip_Enabled: false 92 | Rip_Passive: false 93 | Spanning_Tree_Portfast: false 94 | Speed: 10000000.0 95 | Switchport: false 96 | Switchport_Mode: NONE 97 | Switchport_Trunk_Encapsulation: DOT1Q 98 | VRF: default 99 | VRRP_Groups: [] 100 | Zone_Name: null 101 | GigabitEthernet0/0: 102 | Access_VLAN: null 103 | Active: true 104 | All_Prefixes: 105 | - 10.13.22.3/24 106 | Allowed_VLANs: '' 107 | Auto_State_VLAN: true 108 | Bandwidth: 1000000000.0 109 | Blacklisted: false 110 | Channel_Group: null 111 | Channel_Group_Members: [] 112 | DHCP_Relay_Addresses: [] 113 | Declared_Names: 114 | - GigabitEthernet0/0 115 | Description: null 116 | Encapsulation_VLAN: null 117 | HSRP_Groups: [] 118 | HSRP_Version: null 119 | Incoming_Filter_Name: null 120 | MLAG_ID: null 121 | MTU: 1500 122 | Native_VLAN: null 123 | Outgoing_Filter_Name: null 124 | PBR_Policy_Name: null 125 | Primary_Address: 10.13.22.3/24 126 | Primary_Network: 10.13.22.0/24 127 | Proxy_ARP: true 128 | Rip_Enabled: false 129 | Rip_Passive: false 130 | Spanning_Tree_Portfast: false 131 | Speed: 1000000000.0 132 | Switchport: false 133 | Switchport_Mode: NONE 134 | Switchport_Trunk_Encapsulation: DOT1Q 135 | VRF: default 136 | VRRP_Groups: [] 137 | Zone_Name: null 138 | GigabitEthernet1/0: 139 | Access_VLAN: null 140 | Active: true 141 | All_Prefixes: 142 | - 3.0.2.1/24 143 | Allowed_VLANs: '' 144 | Auto_State_VLAN: true 145 | Bandwidth: 1000000000.0 146 | Blacklisted: false 147 | Channel_Group: null 148 | Channel_Group_Members: [] 149 | DHCP_Relay_Addresses: [] 150 | Declared_Names: 151 | - GigabitEthernet1/0 152 | Description: null 153 | Encapsulation_VLAN: null 154 | HSRP_Groups: [] 155 | HSRP_Version: null 156 | Incoming_Filter_Name: null 157 | MLAG_ID: null 158 | MTU: 1500 159 | Native_VLAN: null 160 | Outgoing_Filter_Name: null 161 | PBR_Policy_Name: null 162 | Primary_Address: 3.0.2.1/24 163 | Primary_Network: 3.0.2.0/24 164 | Proxy_ARP: true 165 | Rip_Enabled: false 166 | Rip_Passive: false 167 | Spanning_Tree_Portfast: false 168 | Speed: 1000000000.0 169 | Switchport: false 170 | Switchport_Mode: NONE 171 | Switchport_Trunk_Encapsulation: DOT1Q 172 | VRF: default 173 | VRRP_Groups: [] 174 | Zone_Name: null 175 | Loopback0: 176 | Access_VLAN: null 177 | Active: true 178 | All_Prefixes: 179 | - 3.2.2.2/32 180 | Allowed_VLANs: '' 181 | Auto_State_VLAN: true 182 | Bandwidth: 8000000000.0 183 | Blacklisted: false 184 | Channel_Group: null 185 | Channel_Group_Members: [] 186 | DHCP_Relay_Addresses: [] 187 | Declared_Names: 188 | - Loopback0 189 | Description: null 190 | Encapsulation_VLAN: null 191 | HSRP_Groups: [] 192 | HSRP_Version: null 193 | Incoming_Filter_Name: null 194 | MLAG_ID: null 195 | MTU: 1500 196 | Native_VLAN: null 197 | Outgoing_Filter_Name: null 198 | PBR_Policy_Name: null 199 | Primary_Address: 3.2.2.2/32 200 | Primary_Network: 3.2.2.2/32 201 | Proxy_ARP: true 202 | Rip_Enabled: false 203 | Rip_Passive: false 204 | Spanning_Tree_Portfast: false 205 | Speed: null 206 | Switchport: false 207 | Switchport_Mode: NONE 208 | Switchport_Trunk_Encapsulation: DOT1Q 209 | VRF: default 210 | VRRP_Groups: [] 211 | Zone_Name: null 212 | NTP: 213 | NTP_Servers: 214 | - 18.18.18.18 215 | - 23.23.23.23 216 | NTP_Source_Interface: null 217 | PBR_Policies: [] 218 | Route6_Filter_Lists: [] 219 | Route_Filter_Lists: 220 | - '101' 221 | - '102' 222 | - '103' 223 | - inbound_route_filter 224 | Routing_Policies: 225 | - as1_to_as3 226 | - as2_to_as3 227 | - as3_to_as1 228 | - as3_to_as2 229 | SNMP: 230 | SNMP_Source_Interface: null 231 | SNMP_Trap_Servers: [] 232 | Syslog: 233 | Logging_Servers: [] 234 | Logging_Source_Interface: null 235 | TACACS: 236 | TACACS_Servers: [] 237 | TACACS_Source_Interface: null 238 | VRFs: 239 | - default 240 | Zones: [] 241 | version: batfish_v0 242 | -------------------------------------------------------------------------------- /tutorials/playbooks/data/validation/as3border1.yml: -------------------------------------------------------------------------------- 1 | nodes: 2 | as3border1: 3 | AS_Path_Access_Lists: [] 4 | Authentication_Key_Chains: [] 5 | BGP: 6 | Multipath_EBGP: true 7 | Multipath_IBGP: true 8 | Multipath_Match_Mode: EXACT_PATH 9 | Neighbors: 10 | 10.23.21.2: 11 | Cluster_ID: null 12 | Export_Policy: 13 | - as3_to_as2 14 | Import_Policy: 15 | - as2_to_as3 16 | Is_Passive: false 17 | Local_AS: 3 18 | Local_IP: 10.23.21.3 19 | Local_Interface: null 20 | Peer_Group: as2 21 | Remote_AS: '2' 22 | Remote_IP: 10.23.21.2 23 | Route_Reflector_Client: false 24 | Send_Community: true 25 | VRF: default 26 | 3.10.1.1: 27 | Cluster_ID: null 28 | Export_Policy: [] 29 | Import_Policy: [] 30 | Is_Passive: false 31 | Local_AS: 3 32 | Local_IP: 3.1.1.1 33 | Local_Interface: null 34 | Peer_Group: as3 35 | Remote_AS: '3' 36 | Remote_IP: 3.10.1.1 37 | Route_Reflector_Client: false 38 | Send_Community: true 39 | VRF: default 40 | Route_Reflector: false 41 | Router_ID: 3.1.1.1 42 | Tie_Breaker: ARRIVAL_ORDER 43 | VRF: default 44 | Configuration_Format: CISCO_IOS 45 | DNS: 46 | DNS_Servers: [] 47 | DNS_Source_Interface: null 48 | Default_Cross_Zone_Action: PERMIT 49 | Default_Inbound_Action: PERMIT 50 | Domain_Name: lab.local 51 | Hostname: as3border1 52 | IP6_Access_Lists: [] 53 | IP_Access_Lists: 54 | - '101' 55 | - '102' 56 | - '103' 57 | IPsec: 58 | IKE_Phase1_Keys: [] 59 | IKE_Phase1_Policies: [] 60 | IKE_Phase1_Proposals: [] 61 | IPsec_Peer_Configs: [] 62 | IPsec_Phase2_Policies: [] 63 | IPsec_Phase2_Proposals: [] 64 | Interfaces: 65 | Ethernet0/0: 66 | Access_VLAN: null 67 | Active: false 68 | All_Prefixes: [] 69 | Allowed_VLANs: '' 70 | Auto_State_VLAN: true 71 | Bandwidth: 10000000.0 72 | Blacklisted: false 73 | Channel_Group: null 74 | Channel_Group_Members: [] 75 | DHCP_Relay_Addresses: [] 76 | Declared_Names: 77 | - Ethernet0/0 78 | Description: null 79 | Encapsulation_VLAN: null 80 | HSRP_Groups: [] 81 | HSRP_Version: null 82 | Incoming_Filter_Name: null 83 | MLAG_ID: null 84 | MTU: 1500 85 | Native_VLAN: null 86 | Outgoing_Filter_Name: null 87 | PBR_Policy_Name: null 88 | Primary_Address: null 89 | Primary_Network: null 90 | Proxy_ARP: true 91 | Rip_Enabled: false 92 | Rip_Passive: false 93 | Spanning_Tree_Portfast: false 94 | Speed: 10000000.0 95 | Switchport: false 96 | Switchport_Mode: NONE 97 | Switchport_Trunk_Encapsulation: DOT1Q 98 | VRF: default 99 | VRRP_Groups: [] 100 | Zone_Name: null 101 | GigabitEthernet0/0: 102 | Access_VLAN: null 103 | Active: true 104 | All_Prefixes: 105 | - 3.0.1.1/24 106 | Allowed_VLANs: '' 107 | Auto_State_VLAN: true 108 | Bandwidth: 1000000000.0 109 | Blacklisted: false 110 | Channel_Group: null 111 | Channel_Group_Members: [] 112 | DHCP_Relay_Addresses: [] 113 | Declared_Names: 114 | - GigabitEthernet0/0 115 | Description: null 116 | Encapsulation_VLAN: null 117 | HSRP_Groups: [] 118 | HSRP_Version: null 119 | Incoming_Filter_Name: null 120 | MLAG_ID: null 121 | MTU: 1500 122 | Native_VLAN: null 123 | Outgoing_Filter_Name: null 124 | PBR_Policy_Name: null 125 | Primary_Address: 3.0.1.1/24 126 | Primary_Network: 3.0.1.0/24 127 | Proxy_ARP: true 128 | Rip_Enabled: false 129 | Rip_Passive: false 130 | Spanning_Tree_Portfast: false 131 | Speed: 1000000000.0 132 | Switchport: false 133 | Switchport_Mode: NONE 134 | Switchport_Trunk_Encapsulation: DOT1Q 135 | VRF: default 136 | VRRP_Groups: [] 137 | Zone_Name: null 138 | GigabitEthernet1/0: 139 | Access_VLAN: null 140 | Active: true 141 | All_Prefixes: 142 | - 10.23.21.3/24 143 | Allowed_VLANs: '' 144 | Auto_State_VLAN: true 145 | Bandwidth: 1000000000.0 146 | Blacklisted: false 147 | Channel_Group: null 148 | Channel_Group_Members: [] 149 | DHCP_Relay_Addresses: [] 150 | Declared_Names: 151 | - GigabitEthernet1/0 152 | Description: null 153 | Encapsulation_VLAN: null 154 | HSRP_Groups: [] 155 | HSRP_Version: null 156 | Incoming_Filter_Name: null 157 | MLAG_ID: null 158 | MTU: 1500 159 | Native_VLAN: null 160 | Outgoing_Filter_Name: null 161 | PBR_Policy_Name: null 162 | Primary_Address: 10.23.21.3/24 163 | Primary_Network: 10.23.21.0/24 164 | Proxy_ARP: true 165 | Rip_Enabled: false 166 | Rip_Passive: false 167 | Spanning_Tree_Portfast: false 168 | Speed: 1000000000.0 169 | Switchport: false 170 | Switchport_Mode: NONE 171 | Switchport_Trunk_Encapsulation: DOT1Q 172 | VRF: default 173 | VRRP_Groups: [] 174 | Zone_Name: null 175 | Loopback0: 176 | Access_VLAN: null 177 | Active: true 178 | All_Prefixes: 179 | - 3.1.1.1/32 180 | Allowed_VLANs: '' 181 | Auto_State_VLAN: true 182 | Bandwidth: 8000000000.0 183 | Blacklisted: false 184 | Channel_Group: null 185 | Channel_Group_Members: [] 186 | DHCP_Relay_Addresses: [] 187 | Declared_Names: 188 | - Loopback0 189 | Description: null 190 | Encapsulation_VLAN: null 191 | HSRP_Groups: [] 192 | HSRP_Version: null 193 | Incoming_Filter_Name: null 194 | MLAG_ID: null 195 | MTU: 1500 196 | Native_VLAN: null 197 | Outgoing_Filter_Name: null 198 | PBR_Policy_Name: null 199 | Primary_Address: 3.1.1.1/32 200 | Primary_Network: 3.1.1.1/32 201 | Proxy_ARP: true 202 | Rip_Enabled: false 203 | Rip_Passive: false 204 | Spanning_Tree_Portfast: false 205 | Speed: null 206 | Switchport: false 207 | Switchport_Mode: NONE 208 | Switchport_Trunk_Encapsulation: DOT1Q 209 | VRF: default 210 | VRRP_Groups: [] 211 | Zone_Name: null 212 | NTP: 213 | NTP_Servers: 214 | - 18.18.18.18 215 | - 23.23.23.23 216 | NTP_Source_Interface: null 217 | PBR_Policies: [] 218 | Route6_Filter_Lists: [] 219 | Route_Filter_Lists: 220 | - '101' 221 | - '102' 222 | - '103' 223 | - default_list 224 | - inbound_route_filter 225 | Routing_Policies: 226 | - as1_to_as3 227 | - as2_to_as3 228 | - as3_to_as1 229 | - as3_to_as2 230 | SNMP: 231 | SNMP_Source_Interface: null 232 | SNMP_Trap_Servers: [] 233 | Syslog: 234 | Logging_Servers: [] 235 | Logging_Source_Interface: null 236 | TACACS: 237 | TACACS_Servers: [] 238 | TACACS_Source_Interface: null 239 | VRFs: 240 | - default 241 | Zones: [] 242 | version: batfish_v0 243 | -------------------------------------------------------------------------------- /library/bf_upload_diagnostics.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # Copyright 2019 The Batfish Open Source Project 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | ANSIBLE_METADATA = { 17 | 'metadata_version': '1.1', 18 | 'status': ['preview'], 19 | 'supported_by': 'community' 20 | } 21 | 22 | # Note: these docs take into account the fact that the plugin handles supplying default values for some params 23 | DOCUMENTATION = ''' 24 | --- 25 | module: bf_upload_diagnostics 26 | short_description: Upload anonymized diagnostic information about a Batfish snapshot 27 | version_added: "2.7" 28 | description: 29 | - Fetches, anonymizes, and uploads diagnostic information about a Batfish snapshot. This runs a series of diagnostic questions on the specified snapshot, which are then anonymized with Netconan (U(https://github.com/intentionet/netconan)), and optionally uploaded to the Batfish developers. By default, passwords are stripped and IP addresses are anonymized. 30 | options: 31 | network: 32 | description: 33 | - Name of the network to collect diagnostic information from. 34 | required: false 35 | default: Value in the C(bf_network) fact. 36 | type: str 37 | snapshot: 38 | description: 39 | - Name of the snapshot to collect diagnostic information about. 40 | required: false 41 | default: Value in the C(bf_snapshot) fact. 42 | type: str 43 | contact_info: 44 | description: 45 | - Contact information associated with this upload. 46 | required: false 47 | type: str 48 | dry_run: 49 | default: true 50 | description: 51 | - Whether or not to skip upload. If C(true), upload is skipped and the anonymized files will be stored locally for review. If C(false), anonymized files will be uploaded to the Batfish developers. 52 | required: false 53 | type: bool 54 | netconan_config: 55 | default: Anonymize passwords and IP addresses. 56 | description: 57 | - Path to Netconan (U(https://github.com/intentionet/netconan)) configuration file, containing settings used for information anonymization. 58 | required: false 59 | type: bool 60 | session: 61 | description: 62 | - Batfish session object required to connect to the Batfish service. 63 | default: Value in the C(bf_session) fact. 64 | required: false 65 | type: dict 66 | author: 67 | - Spencer Fraint (`@sfraint `_) 68 | requirements: 69 | - "pybatfish" 70 | ''' 71 | 72 | EXAMPLES = ''' 73 | # Generate diagnostic information about the specified snapshot and save locally (do not upload) 74 | - bf_upload_diagnostics 75 | network: datacenter_sea 76 | snapshot: 2019-01-01 77 | dry_run: true 78 | contact_info: my.email@example.com 79 | # Generate diagnostic information about the specified snapshot and upload to the Batfish developers 80 | - bf_upload_diagnostics 81 | network: datacenter_sea 82 | snapshot: 2019-01-01 83 | dry_run: false 84 | contact_info: my.email@example.com 85 | ''' 86 | 87 | RETURN = ''' 88 | summary: 89 | description: Summary of action(s) performed. 90 | type: str 91 | returned: always 92 | ''' 93 | 94 | import os 95 | 96 | from ansible.module_utils.basic import AnsibleModule 97 | from ansible.module_utils.bf_util import ( 98 | create_session, get_snapshot_init_warning 99 | ) 100 | 101 | try: 102 | from pybatfish.client.session import Session 103 | except Exception as e: 104 | pybatfish_found = False 105 | else: 106 | pybatfish_found = True 107 | 108 | def run_module(): 109 | # define the available arguments/parameters that a user can pass to 110 | # the module 111 | module_args = dict( 112 | network=dict(type='str', required=True), 113 | snapshot=dict(type='str', required=True), 114 | contact_info=dict(type='str', required=False), 115 | dry_run=dict(type='bool', required=False, default=True), 116 | netconan_config=dict(type='str', required=False), 117 | session=dict(type='dict', required=True), 118 | ) 119 | 120 | # seed the result dict in the object 121 | # we primarily care about changed and state 122 | # change is if this module effectively modified the target 123 | # state will include any data that you want your module to pass back 124 | # for consumption, for example, in a subsequent task 125 | result = dict( 126 | changed=False, 127 | summary='', 128 | ) 129 | 130 | # the AnsibleModule object will be our abstraction working with Ansible 131 | # this includes instantiation, a couple of common attr would be the 132 | # args/params passed to the execution, as well as if the module 133 | # supports check mode 134 | module = AnsibleModule( 135 | argument_spec=module_args, 136 | supports_check_mode=True 137 | ) 138 | 139 | if not pybatfish_found: 140 | module.fail_json(msg='Python module Pybatfish is required') 141 | 142 | if module.check_mode: 143 | return result 144 | 145 | network = module.params['network'] 146 | snapshot = module.params['snapshot'] 147 | contact_info = module.params.get('contact_info') 148 | netconan_config = module.params.get('netconan_config') 149 | dry_run = module.params['dry_run'] 150 | session_params = module.params.get('session', {}) 151 | 152 | try: 153 | session = create_session(**session_params) 154 | except Exception as e: 155 | message = 'Failed to establish session with Batfish service: {}'.format(e) 156 | module.fail_json(msg=message, **result) 157 | return 158 | 159 | try: 160 | session.set_network(network) 161 | session.set_snapshot(snapshot) 162 | except Exception as e: 163 | message = 'Failed to find snapshot {} in network {}: {}'.format(snapshot, network, e) 164 | module.fail_json(msg=message, **result) 165 | 166 | if netconan_config: 167 | if not os.path.isfile(netconan_config): 168 | message = 'Specified netconan_config is invalid. Must specify a file.' 169 | module.fail_json(msg=message, **result) 170 | 171 | try: 172 | upload_result = session.upload_diagnostics(dry_run=dry_run, 173 | netconan_config=netconan_config, 174 | contact_info=contact_info) 175 | if dry_run: 176 | summary = 'Diagnostics for snapshot {} on network {} written to temporary directory: {}'.format( 177 | snapshot, network, upload_result) 178 | else: 179 | summary = 'Diagnostics for snapshot {} on network {} uploaded successfully.'.format( 180 | snapshot, network) 181 | except Exception as e: 182 | message = 'Failed to upload diagnostics: {}'.format(e) 183 | module.fail_json(msg=message, **result) 184 | return 185 | 186 | result['summary'] = summary 187 | module.exit_json(**result) 188 | 189 | def main(): 190 | run_module() 191 | 192 | if __name__ == '__main__': 193 | main() 194 | -------------------------------------------------------------------------------- /library/bf_assert.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # Copyright 2019 The Batfish Open Source Project 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | ANSIBLE_METADATA = { 17 | 'metadata_version': '1.1', 18 | 'status': ['preview'], 19 | 'supported_by': 'community' 20 | } 21 | 22 | # Note: these docs take into account the fact that the plugin handles supplying default values for some params 23 | DOCUMENTATION = ''' 24 | --- 25 | module: bf_assert 26 | short_description: Makes assertions about a Batfish snapshot 27 | version_added: "2.7" 28 | description: 29 | - "Makes assertions about the contents and/or behavior of a Batfish snapshot." 30 | options: 31 | assertions: 32 | description: 33 | - List of assertions to make about the snapshot. 34 | - See U(assertions.rst) for documentation of supported assertions. 35 | required: true 36 | type: list 37 | network: 38 | description: 39 | - Name of the network to make assertions about. 40 | default: Value in the C(bf_network) fact. 41 | required: false 42 | type: str 43 | snapshot: 44 | description: 45 | - Name of the snapshot to make assertions about. 46 | default: Value in the C(bf_snapshot) fact. 47 | required: false 48 | type: str 49 | session: 50 | description: 51 | - Batfish session object required to connect to the Batfish service. 52 | default: Value in the C(bf_session) fact. 53 | required: false 54 | type: dict 55 | author: 56 | - Spencer Fraint (`@sfraint `_) 57 | requirements: 58 | - "pybatfish" 59 | ''' 60 | 61 | EXAMPLES = ''' 62 | # Confirm there are no undefined references or incompatible BGP sessions 63 | - bf_assert: 64 | assertions: 65 | - type: assert_no_undefined_references 66 | name: Confirm we have no undefined references 67 | - type: assert_no_incompatible_bgp_sessions 68 | name: Confirm we have no incompatible BGP sessions 69 | 70 | # Confirm 10.10.10.10 is reachable by traffic entering Gig0/0 of as1border1 71 | - bf_assert: 72 | assertions: 73 | - type: assert_all_flows_succeed 74 | name: confirm host is reachable for traffic received on GigEth0/0 75 | parameters: 76 | startLocation: '@enter(as1border1[GigabitEthernet0/0])' 77 | headers: 78 | dstIps: '10.10.10.10' 79 | 80 | # Confirm a filter denies some specific traffic 81 | - bf_assert: 82 | assertions: 83 | - type: assert_filter_denies 84 | name: confirm node1 filter block_access denies TCP traffic on port 22 85 | parameters: 86 | filters: 'node1["block_access"]' 87 | headers: 88 | applications: 'ssh' 89 | ''' 90 | 91 | RETURN = ''' 92 | summary: 93 | description: Summary of action(s) performed. 94 | type: str 95 | returned: always 96 | result: 97 | description: List of assertion results. There is one entry per assertion, and each entry contains details of the assertion and additional information when the assertion fails. 98 | type: list 99 | returned: always 100 | ''' 101 | 102 | from ansible.module_utils.basic import AnsibleModule 103 | from ansible.module_utils.bf_assertion_util import ( 104 | ASSERT_PASS_MESSAGE, get_assertion_issues, run_assertion 105 | ) 106 | from ansible.module_utils.bf_util import create_session, set_snapshot 107 | 108 | try: 109 | from pybatfish.client.session import Session 110 | except Exception as e: 111 | pybatfish_found = False 112 | else: 113 | pybatfish_found = True 114 | 115 | 116 | def run_module(): 117 | # define the available arguments/parameters that a user can pass to 118 | # the module 119 | module_args = dict( 120 | assertions=dict(type='list', required=False, default='.*'), 121 | network=dict(type='str', required=True), 122 | snapshot=dict(type='str', required=True), 123 | session=dict(type='dict', required=True), 124 | ) 125 | 126 | # seed the result dict in the object 127 | # we primarily care about changed and state 128 | # change is if this module effectively modified the target 129 | # state will include any data that you want your module to pass back 130 | # for consumption, for example, in a subsequent task 131 | result = dict( 132 | changed=False, 133 | result='', 134 | summary='', 135 | ) 136 | 137 | # the AnsibleModule object will be our abstraction working with Ansible 138 | # this includes instantiation, a couple of common attr would be the 139 | # args/params passed to the execution, as well as if the module 140 | # supports check mode 141 | module = AnsibleModule( 142 | argument_spec=module_args, 143 | supports_check_mode=True 144 | ) 145 | 146 | if not pybatfish_found: 147 | module.fail_json(msg='Python module Pybatfish is required') 148 | 149 | assertions = module.params.get('assertions') 150 | session_params = module.params.get('session') 151 | network = module.params.get('network') 152 | snapshot = module.params.get('snapshot') 153 | 154 | try: 155 | session = create_session(**session_params) 156 | except Exception as e: 157 | message = 'Failed to establish session with Batfish service: {}'.format( 158 | e) 159 | module.fail_json(msg=message, **result) 160 | return 161 | 162 | issues = [i for i in [get_assertion_issues(a, session) for a in assertions] 163 | if i] 164 | if any(issues): 165 | message = ( 166 | '{} of {} assertions are malformed'.format(len(issues), 167 | len(assertions)) 168 | + ', no assertions run' 169 | ) 170 | result['result'] = issues 171 | module.fail_json(msg=message, **result) 172 | 173 | if module.check_mode: 174 | module.exit_json(**result) 175 | 176 | results = [] 177 | failed = [] 178 | summary = 'Assertion(s) completed successfully' 179 | 180 | try: 181 | set_snapshot(session=session, network=network, snapshot=snapshot) 182 | except Exception as e: 183 | message = 'Failed to set snapshot: {}'.format(e) 184 | module.fail_json(msg=message, **result) 185 | return 186 | 187 | for assertion in assertions: 188 | status = 'Pass' 189 | try: 190 | assert_result = run_assertion(session, assertion) 191 | if assert_result != ASSERT_PASS_MESSAGE: 192 | failed.append(assert_result) 193 | status = 'Fail' 194 | except Exception as e: 195 | assert_result = str(e) 196 | failed.append(assert_result) 197 | status = 'Error' 198 | 199 | results.append({ 200 | 'name': assertion['name'], 201 | 'type': assertion['type'], 202 | 'status': status, 203 | 'details': assert_result, 204 | }) 205 | if failed: 206 | summary = '{} of {} assertions failed'.format(len(failed), 207 | len(assertions)) 208 | 209 | result['summary'] = summary 210 | result['result'] = results 211 | # Indicate failure to Ansible in the case of failed assert(s) 212 | if failed: 213 | module.fail_json(msg=summary, **result) 214 | 215 | module.exit_json(**result) 216 | 217 | 218 | def main(): 219 | run_module() 220 | 221 | 222 | if __name__ == '__main__': 223 | main() 224 | --------------------------------------------------------------------------------