├── .gitignore ├── LICENSE ├── README.md ├── bonus_lesson_examples ├── collateral │ ├── bgp_jinja_napalm │ │ ├── bgp.j2 │ │ ├── bgp_config.py │ │ └── my_devices.py │ └── os_upgrade │ │ ├── upgrade_device.py │ │ └── upgrade_device_alt.py └── exercises │ ├── exercise1.txt │ ├── exercise2.py │ ├── exercise2_with_threads.py │ ├── exercise3.py │ ├── exercise4.py │ ├── exercise5.py │ ├── my_devices.yml │ └── my_file.txt ├── class1 ├── cisco_ipsec.txt ├── ex10_confparse.py ├── ex3_git_clone.txt ├── ex4_git_addfile.txt ├── ex5_git_branches.txt ├── ex6_yaml_json_write.py ├── ex7_yaml_json_read.py ├── ex8_confparse.py ├── ex9_confparse.py ├── my_test.json └── my_test.yml ├── class2 ├── ex1b_libraries.txt ├── ex1c_lib_path.py ├── ex1c_lib_path.txt ├── ex2_telnetlib.py ├── ex3_telnet_class.py ├── ex4_snmp.py └── my_func.py ├── class3 ├── ex1_run_config_chg.py ├── ex2_snmp_int_graph.py └── line_graph.py ├── class4 ├── config_file.txt ├── ex1_paramiko.py ├── ex2_paramiko.py ├── ex3_pexpect.py ├── ex4_pexpect.py ├── ex5_netmiko.py ├── ex6_netmiko.py ├── ex7_netmiko.py ├── ex8_netmiko.py ├── exercise_notes.txt └── test_devices.py ├── class5 ├── eapi_vlan.py └── ex1_eapi_interfaces.py ├── class6 ├── collateral │ ├── jinja2 │ │ ├── aaa_template.j2 │ │ ├── bgp_ipv4_routes.j2 │ │ ├── bgp_peer_ipv4.j2 │ │ ├── bgp_peer_ipv6.j2 │ │ ├── bgp_template.j2 │ │ ├── cisco1_config.j2 │ │ ├── cisco1_config_2.j2 │ │ ├── intf_config.j2 │ │ ├── intf_config2.j2 │ │ ├── intf_config3.j2 │ │ ├── intf_config_set.j2 │ │ ├── nxos_bgp_include.j2 │ │ ├── nxos_bgp_include_2.j2 │ │ ├── snmp_template.j2 │ │ ├── switch_interfaces.j2 │ │ ├── switch_interfaces2.j2 │ │ ├── switch_interfaces3.j2 │ │ ├── switch_interfaces4.j2 │ │ ├── switch_intf_macro.j2 │ │ ├── test_case10_lists_dicts.py │ │ ├── test_case11_include.py │ │ ├── test_case12_include.py │ │ ├── test_case13_include.py │ │ ├── test_case14_filters.py │ │ ├── test_case15_set.py │ │ ├── test_case16_macros.py │ │ ├── test_case1_basic.py │ │ ├── test_case2_basic.py │ │ ├── test_case3_external_file.py │ │ ├── test_case4_ext_file_formal.py │ │ ├── test_case5_cond.py │ │ ├── test_case6_cond.py │ │ ├── test_case7_for_loop.py │ │ ├── test_case8_nested_for.py │ │ └── test_case9_lists.py │ └── nx-api │ │ ├── case1_json_basic.py │ │ ├── case2_xml_basic.py │ │ ├── case3_pynxos_basic.py │ │ ├── rpc_client.py │ │ └── xml_client.py └── exercises │ ├── arista_template.j2 │ ├── arista_users.j2 │ ├── ex1_jinja_ospf.py │ ├── ex2_if_cond.py │ ├── ex3_for_loop.py │ ├── ex4_arista_include.py │ ├── ex5_pynxos_show.py │ ├── ex6_pynxos_routes.py │ ├── ex7_pynxos_config.py │ ├── nxos_global_ospf.j2 │ ├── nxos_intf.j2 │ ├── ospf_config.j2 │ ├── ospf_config_for.j2 │ └── snmp.j2 ├── class7 ├── CFGS │ ├── eos │ │ ├── dns.j2 │ │ ├── dns.txt │ │ └── templates │ │ │ └── dns.j2 │ ├── ios │ │ ├── dns.j2 │ │ ├── dns.txt │ │ └── templates │ │ │ └── dns.j2 │ └── nxos │ │ ├── dns.j2 │ │ ├── dns.txt │ │ └── templates │ │ └── dns.j2 ├── cisco_merge.txt ├── exercise_notes.text ├── my_devices.py ├── napalm_ex1.py ├── napalm_ex2.py ├── napalm_ex3.py ├── napalm_ex4.py ├── napalm_ex5.py ├── napalm_ex6.py └── napalm_ex6_alt.py ├── class8 ├── django_notes.txt ├── ex1a_db_setup.txt ├── ex1b_link_credentials.py ├── ex1b_link_credentials.txt ├── ex2_vendor_field.py ├── ex3_create_devices.py ├── ex4_delete_devices.py ├── ex5_db_show_version.py ├── ex6_threads_show_ver.py ├── ex7_processes_show_ver.py ├── ex8_proc_w_queue.py └── exercise_notes.txt ├── class9 ├── exercise1 │ ├── mytest │ │ ├── simple.py │ │ ├── whatever.py │ │ └── world.py │ └── test_loading.txt ├── exercise2 │ ├── mytest │ │ ├── __init__.py │ │ ├── simple.py │ │ ├── whatever.py │ │ └── world.py │ └── test_loading.txt ├── exercise3 │ ├── mytest │ │ ├── __init__.py │ │ ├── simple.py │ │ ├── whatever.py │ │ └── world.py │ └── test_loading.txt ├── exercise4 │ └── mytest │ │ ├── __init__.py │ │ ├── simple.py │ │ ├── whatever.py │ │ └── world.py ├── exercise5 │ └── mytest │ │ ├── __init__.py │ │ ├── simple.py │ │ ├── whatever.py │ │ └── world.py ├── exercise6 │ └── mytest │ │ ├── __init__.py │ │ ├── simple.py │ │ ├── whatever.py │ │ └── world.py ├── exercise7 │ ├── exercise_notes.txt │ └── mytest │ │ ├── __init__.py │ │ ├── simple.py │ │ ├── whatever.py │ │ └── world.py ├── exercise8 │ ├── exercise_notes.txt │ └── mytest │ │ ├── __init__.py │ │ ├── simple.py │ │ ├── whatever.py │ │ └── world.py └── exercise9 │ └── ex9_test_script.py ├── setup.cfg └── xml_juniper_bonus ├── collateral ├── case1_xml_parse.py ├── case2_print_xml.py ├── case3_traverse_xml.py ├── case4_xmltodict.py ├── juniper_rpc.py └── show_version.xml └── exercises ├── ex1_xml_lldp.py ├── ex2_xml_arp.py ├── ex3_pyez_facts.py ├── ex4_pyez_eth.py ├── ex5_pyez_routes.py ├── ex6_pyez_change_hostname.py ├── ex7_rpc_show_version.py ├── load_hostname.conf ├── load_hostname.xml ├── show_arp.xml └── show_lldp.xml /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | 103 | class3/netdev.* 104 | class3/*.svg 105 | 106 | *.swp 107 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # python_course 2 | Python Network Automation Course 3 | -------------------------------------------------------------------------------- /bonus_lesson_examples/collateral/bgp_jinja_napalm/bgp.j2: -------------------------------------------------------------------------------- 1 | router bgp {{ local_as }} 2 | {%- for bgp_dict in bgp_peers %} 3 | neighbor {{ bgp_dict.peer_ip }} remote-as {{ bgp_dict.peer_as }} 4 | neighbor {{ bgp_dict.peer_ip }} maximum-routes 12000 5 | {%- endfor %} 6 | -------------------------------------------------------------------------------- /bonus_lesson_examples/collateral/bgp_jinja_napalm/bgp_config.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function, unicode_literals 2 | from jinja2 import FileSystemLoader, StrictUndefined 3 | from jinja2.environment import Environment 4 | 5 | from napalm import get_network_driver 6 | from my_devices import arista1, arista2, arista3, arista4 7 | 8 | env = Environment(undefined=StrictUndefined) 9 | env.loader = FileSystemLoader('.') 10 | 11 | 12 | def gen_bgp_peers(bgp_peer_list): 13 | bgp_return = [] 14 | for peer in bgp_peer_list: 15 | peer_dict = { 16 | 'peer_ip': peer['local_ip'], 17 | 'peer_as': peer['local_as'] 18 | } 19 | bgp_return.append(peer_dict) 20 | 21 | return bgp_return 22 | 23 | 24 | def main(): 25 | sw1 = {'local_as': '100', 'local_ip': '10.220.88.28'} 26 | sw2 = {'local_as': '101', 'local_ip': '10.220.88.29'} 27 | sw3 = {'local_as': '102', 'local_ip': '10.220.88.30'} 28 | sw4 = {'local_as': '103', 'local_ip': '10.220.88.31'} 29 | 30 | sw1_peers = gen_bgp_peers([sw2, sw3, sw4]) 31 | sw2_peers = gen_bgp_peers([sw1, sw3, sw4]) 32 | sw3_peers = gen_bgp_peers([sw1, sw2, sw4]) 33 | sw4_peers = gen_bgp_peers([sw1, sw2, sw3]) 34 | 35 | sw1['bgp_peers'] = sw1_peers 36 | sw2['bgp_peers'] = sw2_peers 37 | sw3['bgp_peers'] = sw3_peers 38 | sw4['bgp_peers'] = sw4_peers 39 | 40 | sw1['napalm_info'] = arista1 41 | sw2['napalm_info'] = arista2 42 | sw3['napalm_info'] = arista3 43 | sw4['napalm_info'] = arista4 44 | 45 | for bgp_vars in [sw1, sw2, sw3, sw4]: 46 | print() 47 | # print(switch_name) 48 | print('-' * 30) 49 | template_file = 'bgp.j2' 50 | template = env.get_template(template_file) 51 | bgp_config = 'no router bgp {}\n'.format(bgp_vars['local_as']) 52 | bgp_config += template.render(bgp_vars) 53 | 54 | # Establish a NAPALM connection 55 | napalm_info = bgp_vars['napalm_info'] 56 | device_type = napalm_info.pop('device_type') 57 | driver = get_network_driver(device_type) 58 | device = driver(**napalm_info) 59 | print() 60 | print(">>>NAPALM Device open") 61 | device.open() 62 | print(device.get_facts()) 63 | 64 | # Use a merge operation to push the config to the device 65 | print() 66 | print(">>>Load config change") 67 | device.load_merge_candidate(config=bgp_config) 68 | 69 | # Generate a diff 70 | print(device.compare_config()) 71 | input("Hit any key to continue: ") 72 | 73 | # Commit the config 74 | device.commit_config() 75 | 76 | 77 | if __name__ == "__main__": 78 | main() 79 | -------------------------------------------------------------------------------- /bonus_lesson_examples/collateral/bgp_jinja_napalm/my_devices.py: -------------------------------------------------------------------------------- 1 | """ 2 | pynet-sw1 (Arista EOS) 3 | pynet-sw2 (Arista EOS) 4 | pynet-sw3 (Arista EOS) 5 | pynet-sw4 (Arista EOS) 6 | """ 7 | from getpass import getpass 8 | 9 | std_pwd = getpass("Enter standard password: ") 10 | 11 | arista1 = { 12 | 'device_type': 'eos', 13 | 'hostname': 'arista1.twb-tech.com', 14 | 'username': 'pyclass', 15 | 'password': std_pwd, 16 | 'optional_args': {}, 17 | } 18 | 19 | arista2 = { 20 | 'device_type': 'eos', 21 | 'hostname': 'arista2.twb-tech.com', 22 | 'username': 'pyclass', 23 | 'password': std_pwd, 24 | 'optional_args': {}, 25 | } 26 | 27 | arista3 = { 28 | 'device_type': 'eos', 29 | 'hostname': 'arista3.twb-tech.com', 30 | 'username': 'pyclass', 31 | 'password': std_pwd, 32 | 'optional_args': {}, 33 | } 34 | 35 | arista4 = { 36 | 'device_type': 'eos', 37 | 'hostname': 'arista4.twb-tech.com', 38 | 'username': 'pyclass', 39 | 'password': std_pwd, 40 | 'optional_args': {}, 41 | } 42 | -------------------------------------------------------------------------------- /bonus_lesson_examples/collateral/os_upgrade/upgrade_device.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from getpass import getpass 3 | from netmiko import ConnectHandler, file_transfer 4 | import re 5 | 6 | 7 | def any_key(): 8 | try: 9 | raw_input("Hit any key to continue: ") 10 | except NameError: 11 | input("Hit any key to continue: ") 12 | print() 13 | 14 | 15 | def main(): 16 | 17 | password = getpass() 18 | 19 | cisco1 = { 20 | 'device_type': 'cisco_ios', 21 | 'host': 'cisco1.twb-tech.com', 22 | 'username': 'pyclass', 23 | 'password': password, 24 | 'file_system': 'flash:' 25 | } 26 | 27 | for net_device in (cisco1,): 28 | 29 | file_system = net_device.pop('file_system') 30 | 31 | # Create the Netmiko SSH connection 32 | ssh_conn = ConnectHandler(**net_device) 33 | print(ssh_conn.find_prompt()) 34 | 35 | # Transfer the IOS image to device 36 | source_file = "test1.bin" 37 | dest_file = "test1.bin" 38 | scp_transfer = False 39 | if scp_transfer: 40 | transfer_dict = file_transfer( 41 | ssh_conn, 42 | source_file=source_file, 43 | dest_file=dest_file, 44 | file_system=file_system, 45 | direction='put', 46 | overwrite_file=False, 47 | ) 48 | 49 | if not transfer_dict['file_exists'] or not transfer_dict['file_verified']: 50 | raise ValueError("The SCP file transfer failed.") 51 | 52 | else: 53 | transfer_dict = {} 54 | 55 | print(transfer_dict) 56 | any_key() 57 | 58 | # Verifying the file transferred correctly 59 | dir_cmd = "dir {}/{}".format(file_system, dest_file) 60 | output = ssh_conn.send_command(dir_cmd) 61 | 62 | if '42628912' not in output or dest_file not in output: 63 | raise ValueError("The SCP file transfer failed.") 64 | print(output) 65 | any_key() 66 | 67 | output = ssh_conn.send_command("show run | inc boot") 68 | print(output) 69 | any_key() 70 | 71 | # boot system flash c880data-universalk9-mz.154-2.T1.bin 72 | match = re.search("^(boot system flash .*.bin)$", output, flags=re.M) 73 | if match: 74 | current_boot = match.group(1) 75 | 76 | # Construct a new boot variable 77 | config_cmds = [ 78 | 'no {}'.format(current_boot), 79 | 'boot system flash test1.bin', 80 | current_boot, 81 | ] 82 | 83 | output = ssh_conn.send_config_set(config_cmds) 84 | 85 | show_boot = ssh_conn.send_command("show run | inc boot") 86 | ssh_conn.save_config() 87 | print(show_boot) 88 | any_key() 89 | 90 | output = ssh_conn.send_command_timing("reload") 91 | if 'confirm' in output: 92 | output += ssh_conn.send_command_timing("y") 93 | 94 | 95 | if __name__ == "__main__": 96 | main() 97 | -------------------------------------------------------------------------------- /bonus_lesson_examples/collateral/os_upgrade/upgrade_device_alt.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Alternate solution to the OS Upgrade Example. This was the one I created during my initial 4 | planning and experimentation. 5 | """ 6 | from __future__ import print_function, unicode_literals 7 | from getpass import getpass 8 | from datetime import datetime 9 | import re 10 | import sys 11 | 12 | from netmiko import ConnectHandler, file_transfer 13 | 14 | 15 | def hit_any_key(): 16 | try: 17 | raw_input("Hit any key to continue: ") 18 | except NameError: 19 | input("Hit any key to continue: ") 20 | 21 | 22 | def verify_image(ssh_conn, file_system, dest_file, file_size): 23 | verify_cmd = 'dir {}/{}'.format(file_system, dest_file) 24 | verify_output = ssh_conn.send_command(verify_cmd) 25 | if file_size in verify_output and dest_file in verify_output: 26 | print() 27 | print(">>>>>") 28 | print("The new image is on the remote device:") 29 | print(verify_output) 30 | print(">>>>>") 31 | print() 32 | hit_any_key() 33 | else: 34 | raise ValueError("New image not detected on remote device.") 35 | 36 | 37 | def check_boot_var(ssh_conn): 38 | """Currently only handles a single boot system statement.""" 39 | current_boot = ssh_conn.send_command("show run | inc boot") 40 | match = re.search(r"^(boot system flash .*)$", current_boot, flags=re.M) 41 | boot_cmd = '' 42 | if match: 43 | boot_cmd = match.group(1) 44 | return boot_cmd 45 | 46 | 47 | def upgrade_device(net_device): 48 | 49 | start_time = datetime.now() 50 | 51 | print() 52 | print("Upgrading OS on device: {}".format(net_device['host'])) 53 | print("-" * 50) 54 | 55 | # Extract file and file system variables 56 | file_system = net_device.pop('file_system') 57 | source_file = net_device.pop('source_file') 58 | dest_file = net_device.pop('dest_file') 59 | 60 | # Establish SSH control channel 61 | print(".establishing SSH connection.") 62 | ssh_conn = ConnectHandler(**net_device) 63 | 64 | # SCP new image file 65 | print(".transferring image file.") 66 | enable_transfer = True 67 | if enable_transfer: 68 | transfer_dict = file_transfer(ssh_conn, source_file=source_file, dest_file=dest_file, 69 | file_system=file_system, direction='put', 70 | overwrite_file=False) 71 | else: 72 | transfer_dict = {} 73 | 74 | # Check the file exists and the MD5 matches the source file 75 | if not transfer_dict.get('file_exists') or not transfer_dict.get('file_verified'): 76 | raise ValueError("File doesn't exist or MD5 doesn't match on the remote system") 77 | 78 | print(".verifying new image file.") 79 | file_size = '42628912' 80 | verify_image(ssh_conn, file_system, dest_file, file_size) 81 | print() 82 | 83 | print(".checking current boot commands") 84 | boot_cmd = check_boot_var(ssh_conn) 85 | 86 | print(".constructing new boot commands") 87 | if boot_cmd: 88 | boot_commands = [ 89 | "no {}".format(boot_cmd), 90 | 'boot system flash {}'.format(dest_file), 91 | boot_cmd, 92 | ] 93 | else: 94 | boot_commands = [ 95 | 'boot system flash {}'.format(dest_file), 96 | ] 97 | 98 | print() 99 | print(">>>>>") 100 | print("Boot commands to send to the remote device:") 101 | print(boot_commands) 102 | print(">>>>>") 103 | print() 104 | hit_any_key() 105 | 106 | print() 107 | print(".sending new boot commands to remote device.") 108 | output = ssh_conn.send_config_set(boot_commands) 109 | print() 110 | 111 | print() 112 | print("Current boot variable: ") 113 | print(">>>>>") 114 | current_boot = ssh_conn.send_command("show run | inc boot") 115 | print(current_boot) 116 | print(">>>>>") 117 | print() 118 | 119 | # Reload the device 120 | print() 121 | try: 122 | response = raw_input("Do you want to reload the device(y/n): ") 123 | except NameError: 124 | response = input("Do you want to reload the device(y/n): ") 125 | 126 | if response.lower() != 'y': 127 | sys.exit("Boot commands staged, but device not reloaded!\n\n") 128 | else: 129 | print("Saving running-config to startup-config") 130 | ssh_conn.save_config() 131 | print("Reloading device with new image!") 132 | output = ssh_conn.send_command_timing("reload") 133 | print(output) 134 | if 'confirm' in output: 135 | output = ssh_conn.send_command_timing("y") 136 | 137 | end_time = datetime.now() 138 | print("File transfer time: {}".format(end_time - start_time)) 139 | 140 | 141 | if __name__ == "__main__": 142 | 143 | password = getpass() 144 | 145 | cisco1 = { 146 | 'device_type': 'cisco_ios', 147 | 'host': 'cisco1.twb-tech.com', 148 | 'username': 'pyclass', 149 | 'password': password, 150 | 'file_system': 'flash:', 151 | 'source_file': 'test1.bin', 152 | 'dest_file': 'test1.bin', 153 | } 154 | 155 | for net_device in (cisco1,): 156 | upgrade_device(net_device) 157 | -------------------------------------------------------------------------------- /bonus_lesson_examples/exercises/exercise1.txt: -------------------------------------------------------------------------------- 1 | # 2 | # GIT Exercises 3 | # 4 | # a. Create a repository named 'python_course_test1' in GitHub. Make sure this repository has a README.md file. 5 | # 6 | # b. Git clone that repository into the lab environment. 7 | # 8 | # c. Use 'git add' and 'git commit' to modify the README.md file. 9 | # 10 | # d. Use 'git add' to add another python script into the repository. 11 | # 12 | # e. Use 'git rm' to delete that Python script from the repository. 13 | # 14 | # f. Use 'git push' to push the updated repository back up to GitHub. 15 | # 16 | # g. In GitHub, fork the python_course repository (https://github.com/ktbyers/python_course). 17 | # 18 | # h. Clone this repository (your fork) onto the AWS server 19 | # 20 | # i. Create a new 'bonus' branch for this repository. 21 | # 22 | # j. Make a modification to the README.md file in this branch and commit the change. 23 | # 24 | # k. Push your new 'bonus' branch back up to GitHub (verify the branch and your change are now on GitHub). 25 | # 26 | # l. Switch back to the master branch 27 | # 28 | 29 | 30 | >>> 1a. Go to github.com/user (i.e. your GitHub page) 31 | 32 | Click on the '+' in the upper right corner. 33 | 34 | Name it python_course_test1 35 | a. Public 36 | b. Initialize with a README 37 | c. Set .gitignore to Python 38 | d. Set license to Apache2 (for open-source projects) 39 | e. Create repository 40 | 41 | 42 | >>> 1b. 43 | 44 | cd ~ 45 | $ git clone https://github.com/ktbyers/python_course_test1 46 | Cloning into 'python_course_test1'... 47 | remote: Counting objects: 5, done. 48 | remote: Compressing objects: 100% (4/4), done. 49 | remote: Total 5 (delta 0), reused 0 (delta 0), pack-reused 0 50 | Unpacking objects: 100% (5/5), done. 51 | 52 | $ cd python_course_test1/ 53 | $ ls 54 | LICENSE README.md 55 | 56 | 57 | >>> 1c. 58 | 59 | $ vi README.md 60 | 61 | $ git status 62 | On branch master 63 | Your branch is up-to-date with 'origin/master'. 64 | Changes not staged for commit: 65 | (use "git add ..." to update what will be committed) 66 | (use "git checkout -- ..." to discard changes in working directory) 67 | 68 | modified: README.md 69 | 70 | no changes added to commit (use "git add" and/or "git commit -a") 71 | 72 | $ git add README.md 73 | $ git commit -m "Update the README" 74 | [master 93af030] Update the README 75 | 1 file changed, 3 insertions(+), 1 deletion(-) 76 | 77 | 78 | >>> 1d. 79 | 80 | $ vi test1.py 81 | $ python test1.py 82 | Hello Python3 83 | 84 | $ git add test1.py 85 | $ git commit -m "Add Python script" 86 | [master 4f88ede] Add Python script 87 | 1 file changed, 1 insertion(+) 88 | create mode 100644 test1.py 89 | 90 | 91 | >>> 1e. 92 | $ git status 93 | On branch master 94 | Your branch is ahead of 'origin/master' by 2 commits. 95 | (use "git push" to publish your local commits) 96 | nothing to commit, working tree clean 97 | 98 | $ git rm test1.py 99 | rm 'test1.py' 100 | 101 | $ ls 102 | LICENSE README.md 103 | 104 | $ git status 105 | On branch master 106 | Your branch is ahead of 'origin/master' by 2 commits. 107 | (use "git push" to publish your local commits) 108 | Changes to be committed: 109 | (use "git reset HEAD ..." to unstage) 110 | 111 | deleted: test1.py 112 | 113 | $ git commit -m "Delete test1.py" 114 | [master 36e015f] Delete test1.py 115 | 1 file changed, 1 deletion(-) 116 | delete mode 100644 test1.py 117 | 118 | 119 | >>> 1f. 120 | 121 | $ git status 122 | On branch master 123 | Your branch is ahead of 'origin/master' by 3 commits. 124 | (use "git push" to publish your local commits) 125 | nothing to commit, working tree clean 126 | 127 | $ git remote -v 128 | origin https://github.com/ktbyers/python_course_test1 (fetch) 129 | origin https://github.com/ktbyers/python_course_test1 (push) 130 | 131 | $ git push origin master 132 | Username for 'https://github.com': 133 | Password for 'https://your_user@github.com': 134 | Counting objects: 7, done. 135 | Compressing objects: 100% (5/5), done. 136 | Writing objects: 100% (7/7), 699 bytes | 699.00 KiB/s, done. 137 | Total 7 (delta 2), reused 0 (delta 0) 138 | remote: Resolving deltas: 100% (2/2), done. 139 | To https://github.com/ktbyers/python_course_test1 140 | 5e6c702..36e015f master -> master 141 | 142 | There are now four commits on the master branch on GitHub 143 | 144 | 145 | >>> 1g. Click the fork button on the ktbyers/python_course repository 146 | 147 | 148 | >>> 1h. 149 | 150 | $ git clone https://github.com//python_course 151 | Cloning into 'python_course'... 152 | remote: Counting objects: 487, done. 153 | remote: Compressing objects: 100% (315/315), done. 154 | remote: Total 487 (delta 211), reused 422 (delta 149), pack-reused 0 155 | Receiving objects: 100% (487/487), 96.42 KiB | 4.02 MiB/s, done. 156 | Resolving deltas: 100% (211/211), done. 157 | 158 | 159 | >>> 1i. 160 | 161 | # Create the new branch and checkout in one action 162 | $ git checkout -b bonus origin 163 | Branch bonus set up to track remote branch master from origin. 164 | Switched to a new branch 'bonus' 165 | 166 | $ git branch 167 | * bonus 168 | master 169 | 170 | 171 | >>> 1j. 172 | $ vi README.md 173 | 174 | $ git status 175 | On branch bonus 176 | Your branch is up-to-date with 'origin/master'. 177 | Changes not staged for commit: 178 | (use "git add ..." to update what will be committed) 179 | (use "git checkout -- ..." to discard changes in working directory) 180 | 181 | modified: README.md 182 | 183 | no changes added to commit (use "git add" and/or "git commit -a") 184 | 185 | $ git add README.md 186 | $ git commit -m "Test change to the bonus branch" 187 | [bonus 98b6cbf] Test change to the bonus branch 188 | 1 file changed, 2 insertions(+) 189 | 190 | 191 | >>> 1k. 192 | $ git status 193 | On branch bonus 194 | Your branch is ahead of 'origin/master' by 1 commit. 195 | (use "git push" to publish your local commits) 196 | nothing to commit, working tree clean 197 | 198 | $ git push origin bonus 199 | Username for 'https://github.com': 200 | Password for 'https://your_user@github.com': 201 | Counting objects: 3, done. 202 | Compressing objects: 100% (3/3), done. 203 | Writing objects: 100% (3/3), 340 bytes | 340.00 KiB/s, done. 204 | Total 3 (delta 1), reused 0 (delta 0) 205 | remote: Resolving deltas: 100% (1/1), completed with 1 local object. 206 | To https://github.com/ktbyers/python_course 207 | * [new branch] bonus -> bonus 208 | 209 | This branch now exists on GitHub 210 | https://github.com/ktbyers/python_course/tree/bonus 211 | 212 | 213 | >>> 1l. 214 | 215 | $ git branch 216 | * bonus 217 | master 218 | 219 | $ git checkout master 220 | Switched to branch 'master' 221 | Your branch is up-to-date with 'origin/master'. 222 | 223 | $ git status 224 | On branch master 225 | Your branch is up-to-date with 'origin/master'. 226 | nothing to commit, working tree clean 227 | 228 | $ git branch 229 | bonus 230 | * master 231 | 232 | 233 | -------------------------------------------------------------------------------- /bonus_lesson_examples/exercises/exercise2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Create a file named 'my_file.txt' with some random content in it. 4 | 5 | Use Netmiko's secure copy to transfer this file to the Arista1, Arista2, Arista3, and Arista4 6 | switches. 7 | 8 | Note, you will need to use PIP to upgrade Netmiko in the lab environment to Netmiko 2.1.1 to 9 | complete this exercise: 10 | 11 | pip install netmiko==2.1.1 12 | 13 | At the end of the transfer make sure the file exists and the MD5 passed. Netmiko should report 14 | this back to you as part of its file_transfer function. 15 | 16 | Optional Bonus: Use threads so this transfer happens to all four switches concurrently. 17 | """ 18 | from __future__ import print_function, unicode_literals 19 | from getpass import getpass 20 | from netmiko import ConnectHandler, file_transfer 21 | 22 | 23 | def create_device_dict(hostname, password): 24 | return { 25 | 'device_type': 'arista_eos', 26 | 'host': hostname, 27 | 'username': 'pyclass', 28 | 'password': password, 29 | 'file_system': "/mnt/flash", 30 | } 31 | 32 | 33 | def main(): 34 | password = getpass() 35 | 36 | hostnames = [ 37 | 'arista1.twb-tech.com', 38 | 'arista2.twb-tech.com', 39 | 'arista3.twb-tech.com', 40 | 'arista4.twb-tech.com', 41 | ] 42 | 43 | for host in hostnames: 44 | net_device = create_device_dict(host, password) 45 | file_system = net_device.pop('file_system') 46 | 47 | # Create the Netmiko SSH connection 48 | ssh_conn = ConnectHandler(**net_device) 49 | print() 50 | print(">>>>>") 51 | print(ssh_conn.find_prompt()) 52 | 53 | # Transfer the IOS image to device 54 | source_file = "my_file.txt" 55 | dest_file = "my_file.txt" 56 | 57 | transfer_dict = file_transfer( 58 | ssh_conn, 59 | source_file=source_file, 60 | dest_file=dest_file, 61 | file_system=file_system, 62 | direction='put', 63 | overwrite_file=False, 64 | ) 65 | 66 | md5_check = transfer_dict['file_verified'] 67 | file_exists = transfer_dict['file_exists'] 68 | 69 | if md5_check and file_exists: 70 | print("File successfully transferred to: {host}".format(**net_device)) 71 | else: 72 | print("Failure on SCP: {host} !!!".format(**net_device)) 73 | print(">>>>>") 74 | 75 | 76 | if __name__ == "__main__": 77 | main() 78 | -------------------------------------------------------------------------------- /bonus_lesson_examples/exercises/exercise2_with_threads.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Create a file named 'my_file.txt' with some random content in it. 4 | 5 | Use Netmiko's secure copy to transfer this file to the Arista1, Arista2, Arista3, and Arista4 6 | switches. 7 | 8 | Note, you will need to use PIP to upgrade Netmiko in the lab environment to Netmiko 2.1.1 to 9 | complete this exercise: 10 | 11 | pip install netmiko==2.1.1 12 | 13 | At the end of the transfer make sure the file exists and the MD5 passed. Netmiko should report 14 | this back to you as part of its file_transfer function. 15 | 16 | Optional Bonus: Use threads so this transfer happens to all four switches concurrently. 17 | """ 18 | from __future__ import print_function, unicode_literals 19 | import threading 20 | from datetime import datetime 21 | from getpass import getpass 22 | from netmiko import ConnectHandler, file_transfer 23 | 24 | 25 | def create_device_dict(hostname, password): 26 | return { 27 | 'device_type': 'arista_eos', 28 | 'host': hostname, 29 | 'username': 'pyclass', 30 | 'password': password, 31 | 'file_system': "/mnt/flash", 32 | } 33 | 34 | 35 | def scp_file(net_device): 36 | # Make a copy of the dictionary since we modify it 37 | device_dict = net_device.copy() 38 | file_system = device_dict.pop('file_system') 39 | 40 | # Create the Netmiko SSH connection 41 | ssh_conn = ConnectHandler(**device_dict) 42 | 43 | # Transfer the IOS image to device 44 | source_file = "my_file.txt" 45 | dest_file = "my_file.txt" 46 | 47 | transfer_dict = file_transfer( 48 | ssh_conn, 49 | source_file=source_file, 50 | dest_file=dest_file, 51 | file_system=file_system, 52 | direction='put', 53 | overwrite_file=False, 54 | ) 55 | 56 | md5_check = transfer_dict['file_verified'] 57 | file_exists = transfer_dict['file_exists'] 58 | 59 | if md5_check and file_exists: 60 | print("File successfully transferred to: {host}".format(**net_device)) 61 | else: 62 | print("Failure on SCP: {host} !!!".format(**net_device)) 63 | 64 | ssh_conn.disconnect() 65 | 66 | 67 | def main(): 68 | password = getpass() 69 | start_time = datetime.now() 70 | 71 | hostnames = [ 72 | 'arista1.twb-tech.com', 73 | 'arista2.twb-tech.com', 74 | 'arista3.twb-tech.com', 75 | 'arista4.twb-tech.com', 76 | ] 77 | 78 | print() 79 | print(">>>>>") 80 | for host in hostnames: 81 | net_device = create_device_dict(host, password) 82 | my_thread = threading.Thread(target=scp_file, args=(net_device,)) 83 | my_thread.start() 84 | 85 | main_thread = threading.currentThread() 86 | for some_thread in threading.enumerate(): 87 | if some_thread != main_thread: 88 | some_thread.join() 89 | print(">>>>>") 90 | 91 | print("\nElapsed time: " + str(datetime.now() - start_time)) 92 | 93 | 94 | if __name__ == "__main__": 95 | main() 96 | -------------------------------------------------------------------------------- /bonus_lesson_examples/exercises/exercise3.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Update exercise2 such that you store the definition for the four Arista devices in an external 4 | YAML file that you read in and parse upon script execution. 5 | 6 | You should use the device definition in this external YAML file to make the Netmiko connections. 7 | In other words, make your key-value pairs in your YAML dictionary match the key-value pairs that 8 | you require for Netmiko. 9 | 10 | Don't include the password in your YAML file. Add the password file using getpass() to the device 11 | dictionary inside of your Python program. 12 | """ 13 | from __future__ import print_function, unicode_literals 14 | from getpass import getpass 15 | import yaml 16 | from netmiko import ConnectHandler, file_transfer 17 | 18 | 19 | def read_yaml(filename): 20 | with open(filename) as f: 21 | return yaml.load(f) 22 | 23 | 24 | def main(): 25 | password = getpass() 26 | 27 | filename = 'my_devices.yml' 28 | my_devices = read_yaml(filename) 29 | 30 | for hostname, net_device in my_devices.items(): 31 | file_system = net_device.pop('file_system') 32 | net_device['password'] = password 33 | 34 | # Create the Netmiko SSH connection 35 | ssh_conn = ConnectHandler(**net_device) 36 | print() 37 | print(">>>>>") 38 | print(ssh_conn.find_prompt()) 39 | 40 | # Transfer the IOS image to device 41 | source_file = "my_file.txt" 42 | dest_file = "my_file.txt" 43 | 44 | transfer_dict = file_transfer( 45 | ssh_conn, 46 | source_file=source_file, 47 | dest_file=dest_file, 48 | file_system=file_system, 49 | direction='put', 50 | overwrite_file=False, 51 | ) 52 | 53 | md5_check = transfer_dict['file_verified'] 54 | file_exists = transfer_dict['file_exists'] 55 | 56 | if md5_check and file_exists: 57 | print("File successfully transferred to: {host}".format(**net_device)) 58 | else: 59 | print("Failure on SCP: {host} !!!".format(**net_device)) 60 | print(">>>>>") 61 | 62 | 63 | if __name__ == "__main__": 64 | main() 65 | -------------------------------------------------------------------------------- /bonus_lesson_examples/exercises/exercise4.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Use Netmiko and TextFSM to retrieve 'show ip int brief' from pynet-rtr2 as structured 4 | data. 5 | 6 | This will require that you install ntc-templates into the lab environment as follows: 7 | 8 | # cd to the base of your home directory 9 | $ cd ~ 10 | 11 | # Git clone ntc-templates into this directory 12 | $ git clone https://github.com/networktocode/ntc-templates 13 | 14 | # At the end of this you should have ntc-templates on this path 15 | $ ls ~/ntc-templates/templates/index 16 | 17 | In order to do this, besides adding ntc-templates into the above path, you will also 18 | need to add the 'use_textfsm=True' argument when calling the send_command() method. 19 | 20 | Note, if your TextFSM lookup fails, you will probably get unstructured data returned to 21 | you. You can also set this environment variable (if your TextFSM lookup is failing). 22 | 23 | export NET_TEXTFSM=/path/to/ntc-templates/templates/ 24 | 25 | After you have retrieved this 'show ip int brief' output as structured data. Parse the 26 | returned data structure and print out the IP address associated with FastEthernet4. 27 | """ 28 | 29 | from __future__ import print_function, unicode_literals 30 | from getpass import getpass 31 | from netmiko import ConnectHandler 32 | 33 | password = getpass() 34 | 35 | pynet_rtr2 = { 36 | 'device_type': 'cisco_ios', 37 | 'host': 'cisco2.twb-tech.com', 38 | 'username': 'pyclass', 39 | 'password': password, 40 | } 41 | 42 | net_connect = ConnectHandler(**pynet_rtr2) 43 | show_ip = net_connect.send_command('show ip int brief', use_textfsm=True) 44 | 45 | print() 46 | print(">>>>>") 47 | for intf_dict in show_ip: 48 | if intf_dict['intf'] == 'FastEthernet4': 49 | print("IP address of FA4: {ipaddr}".format(**intf_dict)) 50 | print(">>>>>") 51 | print() 52 | -------------------------------------------------------------------------------- /bonus_lesson_examples/exercises/exercise5.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Create NAPALM a script that connects to srx1 and retrieves its LLDP information 4 | (get_lldp_neighbors() method). 5 | 6 | From this returned data structure retrieve the local interface name, the remote switch name that 7 | this srx1 is connected to, and the remote port name. 8 | 9 | Email this local interface, remote switch name, and remote port name to yourself using the 10 | send_mail function from earlier in the course. 11 | """ 12 | from __future__ import print_function, unicode_literals 13 | from getpass import getpass 14 | from napalm import get_network_driver 15 | from email_helper import send_mail 16 | 17 | 18 | def main(): 19 | password = getpass() 20 | 21 | srx1 = { 22 | 'device_type': 'junos', 23 | 'hostname': 'srx1.twb-tech.com', 24 | 'username': 'pyclass', 25 | 'password': password, 26 | 'optional_args': {}, 27 | } 28 | 29 | for net_device in (srx1,): 30 | device_type = net_device.pop('device_type') 31 | driver = get_network_driver(device_type) 32 | 33 | # Establish a NAPALM connection 34 | device = driver(**net_device) 35 | device.open() 36 | lldp_neighbors = device.get_lldp_neighbors() 37 | 38 | # Should only be one entry 39 | for local_intf, lldp_list in lldp_neighbors.items(): 40 | remote_lldp = lldp_list[0] 41 | remote_host = remote_lldp['hostname'] 42 | remote_port = remote_lldp['port'] 43 | break 44 | 45 | # Send email 46 | msg = """ 47 | SRX1 is connected on local intf: {local_intf} 48 | 49 | To remote host: {remote_host} 50 | On remote port: {remote_port} 51 | """.format(local_intf=local_intf, remote_host=remote_host, remote_port=remote_port) 52 | 53 | recipient = 'ktbyers@twb-tech.com' 54 | sender = 'twb@twb-tech.com' 55 | subject = 'Bonus lesson LLDP exercise' 56 | send_mail(recipient, subject, message=msg, sender=sender) 57 | print() 58 | print("Message sent...check your inbox.") 59 | print() 60 | 61 | 62 | if __name__ == "__main__": 63 | main() 64 | -------------------------------------------------------------------------------- /bonus_lesson_examples/exercises/my_devices.yml: -------------------------------------------------------------------------------- 1 | --- 2 | arista1: 3 | device_type: arista_eos 4 | host: arista1.twb-tech.com 5 | username: pyclass 6 | file_system: /mnt/flash 7 | 8 | arista2: 9 | device_type: arista_eos 10 | host: arista2.twb-tech.com 11 | username: pyclass 12 | file_system: /mnt/flash 13 | 14 | arista3: 15 | device_type: arista_eos 16 | host: arista3.twb-tech.com 17 | username: pyclass 18 | file_system: /mnt/flash 19 | 20 | arista4: 21 | device_type: arista_eos 22 | host: arista4.twb-tech.com 23 | username: pyclass 24 | file_system: /mnt/flash 25 | -------------------------------------------------------------------------------- /bonus_lesson_examples/exercises/my_file.txt: -------------------------------------------------------------------------------- 1 | SCP testing 2 | -------------------------------------------------------------------------------- /class1/cisco_ipsec.txt: -------------------------------------------------------------------------------- 1 | ! 2 | ! Last configuration change at 16:34:34 PDT Wed Jul 1 2015 by pyclass 3 | ! NVRAM config last updated at 16:30:19 PDT Wed Jul 1 2015 by pyclass 4 | ! 5 | version 15.4 6 | no service pad 7 | service timestamps debug datetime msec localtime show-timezone 8 | service timestamps log datetime msec localtime show-timezone 9 | no service password-encryption 10 | ! 11 | hostname pynet-rtr1 12 | ! 13 | ! 14 | ! 15 | logging buffered 50000 16 | no logging console 17 | enable secret 5 $1$5999$djwfej9Mq99917x9SDgAC. 18 | ! 19 | aaa new-model 20 | ! 21 | ! 22 | aaa authentication login default local 23 | aaa authorization exec default local if-authenticated 24 | ! 25 | ! 26 | ! 27 | ! 28 | ! 29 | aaa session-id common 30 | memory-size iomem 10 31 | clock timezone PST -8 0 32 | clock summer-time PDT recurring 33 | ! 34 | ! 35 | ! 36 | ! 37 | ! 38 | ! 39 | ! 40 | ! 41 | ! 42 | ! 43 | ! 44 | ! 45 | ! 46 | no ip domain lookup 47 | ip domain name twb-tech.com 48 | ip cef 49 | no ipv6 cef 50 | ! 51 | ! 52 | multilink bundle-name authenticated 53 | license udi pid CISCO881-SEC-K9 sn FTX1000000X 54 | ! 55 | ! 56 | username pyclass privilege 15 secret 5 $1$CQCe$poonB1jFHYQAWt/3eXub41 57 | ! 58 | ! 59 | ! 60 | ! 61 | lldp run 62 | ! 63 | ip ssh version 2 64 | ip scp server enable 65 | ! 66 | ! 67 | crypto isakmp policy 10 68 | encr aes 69 | authentication pre-share 70 | group 5 71 | crypto isakmp key KEY address 1.1.1.1 no-xauth 72 | crypto isakmp key KEY address 2.2.2.1 no-xauth 73 | crypto isakmp key KEY address 3.3.3.1 no-xauth 74 | crypto isakmp key KEY address 4.4.4.1 no-xauth 75 | crypto isakmp key KEY address 5.5.5.1 no-xauth 76 | crypto isakmp keepalive 10 periodic 77 | ! 78 | ! 79 | crypto ipsec transform-set AES192-SHA esp-aes 192 esp-sha-hmac 80 | mode tunnel 81 | crypto ipsec transform-set AES-SHA esp-aes esp-sha-hmac 82 | mode tunnel 83 | crypto ipsec transform-set 3DES-SHA esp-3des esp-sha-hmac 84 | mode tunnel 85 | ! 86 | ! 87 | ! 88 | crypto map CRYPTO 10 ipsec-isakmp 89 | set peer 1.1.1.1 90 | set transform-set AES-SHA 91 | set pfs group5 92 | match address VPN-TEST1 93 | crypto map CRYPTO 20 ipsec-isakmp 94 | set peer 2.2.2.1 95 | set transform-set AES-SHA 96 | set pfs group2 97 | match address VPN-TEST2 98 | crypto map CRYPTO 30 ipsec-isakmp 99 | set peer 3.3.3.1 100 | set transform-set AES-SHA 101 | set pfs group2 102 | match address VPN-TEST3 103 | crypto map CRYPTO 40 ipsec-isakmp 104 | set peer 4.4.4.1 105 | set transform-set AES-SHA 106 | set pfs group5 107 | match address VPN-TEST4 108 | crypto map CRYPTO 50 ipsec-isakmp 109 | set peer 5.5.5.1 110 | set transform-set 3DES-SHA 111 | set pfs group5 112 | match address VPN-TEST5 113 | ! 114 | ! 115 | ! 116 | ! 117 | interface FastEthernet0 118 | no ip address 119 | ! 120 | interface FastEthernet1 121 | no ip address 122 | ! 123 | interface FastEthernet2 124 | no ip address 125 | ! 126 | interface FastEthernet3 127 | no ip address 128 | ! 129 | interface FastEthernet4 130 | description *** LAN connection (don't change) *** 131 | ip address 10.220.88.20 255.255.255.0 132 | duplex auto 133 | speed auto 134 | ! 135 | interface Vlan1 136 | no ip address 137 | ! 138 | ip forward-protocol nd 139 | no ip http server 140 | no ip http secure-server 141 | ! 142 | ! 143 | ip route 0.0.0.0 0.0.0.0 10.220.88.1 144 | ! 145 | ! 146 | ! 147 | ! 148 | ! 149 | control-plane 150 | ! 151 | ! 152 | ! 153 | line con 0 154 | no modem enable 155 | line aux 0 156 | line vty 0 4 157 | transport input ssh 158 | ! 159 | scheduler max-task-time 5000 160 | ntp server 130.126.24.24 161 | ntp server 152.2.21.1 162 | ! 163 | end 164 | -------------------------------------------------------------------------------- /class1/ex10_confparse.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Using ciscoconfparse find the crypto maps that are not using AES (based-on the 4 | transform set name). Print these entries and their corresponding transform set 5 | name. 6 | """ 7 | from __future__ import unicode_literals, print_function 8 | import re 9 | from ciscoconfparse import CiscoConfParse 10 | 11 | 12 | def main(): 13 | """ 14 | Using ciscoconfparse find the crypto maps that are not using AES (based-on 15 | the transform set name). Print these entries and their corresponding 16 | transform set name. 17 | """ 18 | cisco_file = 'cisco_ipsec.txt' 19 | 20 | cisco_cfg = CiscoConfParse(cisco_file) 21 | crypto_maps = cisco_cfg.find_objects_wo_child(parentspec=r'crypto map CRYPTO', 22 | childspec=r'AES') 23 | print("\nCrypto maps not using AES:") 24 | for entry in crypto_maps: 25 | for child in entry.children: 26 | if 'transform' in child.text: 27 | match = re.search(r"set transform-set (.*)$", child.text) 28 | encryption = match.group(1) 29 | print(" {} >>> {}".format(entry.text.strip(), encryption)) 30 | print() 31 | 32 | 33 | if __name__ == "__main__": 34 | main() 35 | -------------------------------------------------------------------------------- /class1/ex3_git_clone.txt: -------------------------------------------------------------------------------- 1 | # Clone the repository that you just created on GitHub into your home 2 | # directory in the lab environment. 3 | 4 | $ git clone https://github.com/ktbyers/pynet_test 5 | Cloning into 'pynet_test'... 6 | remote: Counting objects: 5, done. 7 | remote: Compressing objects: 100% (4/4), done. 8 | remote: Total 5 (delta 0), reused 0 (delta 0), pack-reused 0 9 | Unpacking objects: 100% (5/5), done. 10 | 11 | 12 | $ cd pynet_test 13 | $ ls -al 14 | total 32 15 | drwxrwxr-x. 3 kbyers kbyers 64 Jul 1 15:03 . 16 | drwx------. 50 kbyers kbyers 4096 Jul 1 15:03 .. 17 | drwxrwxr-x. 8 kbyers kbyers 4096 Jul 1 15:03 .git 18 | -rw-rw-r--. 1 kbyers kbyers 702 Jul 1 15:03 .gitignore 19 | -rw-rw-r--. 1 kbyers kbyers 11358 Jul 1 15:03 LICENSE 20 | -rw-rw-r--. 1 kbyers kbyers 13 Jul 1 15:03 README.md 21 | 22 | -------------------------------------------------------------------------------- /class1/ex4_git_addfile.txt: -------------------------------------------------------------------------------- 1 | # Add a file to the repository in the lab environment and then push it up to 2 | # GitHub. 3 | 4 | $ vi test_enumerate.py 5 | $ chmod 755 test_enumerate.py 6 | 7 | $ git add test_enumerate.py 8 | $ git commit -m "Adding test_enumerate.py file" 9 | [master 4efcbaa] Adding test_enumerate.py file 10 | 1 file changed, 7 insertions(+) 11 | create mode 100755 test_enumerate.py 12 | 13 | $ git push origin master 14 | Username for 'https://github.com': ktbyers 15 | Password for 'https://ktbyers@github.com': 16 | Counting objects: 4, done. 17 | Compressing objects: 100% (3/3), done. 18 | Writing objects: 100% (3/3), 428 bytes | 0 bytes/s, done. 19 | Total 3 (delta 1), reused 0 (delta 0) 20 | To https://github.com/ktbyers/pynet_test 21 | 76a950e..4efcbaa master -> master 22 | -------------------------------------------------------------------------------- /class1/ex5_git_branches.txt: -------------------------------------------------------------------------------- 1 | # Create a 'test' branch in your repository. 2 | # a. Switch between the 'master' branch and the 'test' branch. 3 | # b. Add a file to the 'test' branch. 4 | # c. Switch back to the 'master' branch. 5 | # d. Merge this 'test' branch into your 'master' branch. 6 | 7 | ### 5 8 | $ git branch 9 | * master 10 | 11 | $ git branch test 12 | $ git branch 13 | * master 14 | test 15 | 16 | 17 | ### 5a 18 | $ git checkout test 19 | Switched to branch 'test' 20 | 21 | $ git branch 22 | master 23 | * test 24 | 25 | 26 | ### 5b 27 | $ vi test_zip.py 28 | $ chmod 755 test_zip.py 29 | 30 | $ git add test_zip.py 31 | $ git commit -m "Adding test_zip.py file" 32 | [test c6ebab9] Adding test_zip.py file 33 | 1 file changed, 7 insertions(+) 34 | create mode 100755 test_zip.py 35 | 36 | 37 | ### 5c 38 | $ git checkout master 39 | Switched to branch 'master' 40 | 41 | $ git branch 42 | * master 43 | test 44 | 45 | 46 | ### 5d 47 | $ git merge test 48 | Updating 4efcbaa..c6ebab9 49 | Fast-forward 50 | test_zip.py | 7 +++++++ 51 | 1 file changed, 7 insertions(+) 52 | create mode 100755 test_zip.py 53 | -------------------------------------------------------------------------------- /class1/ex6_yaml_json_write.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Write a Python program that creates a list. One of the elements of the list 4 | should be a dictionary with at least two keys. Write this list out to a file 5 | using both YAML and JSON formats. The YAML file should be in the expanded form. 6 | """ 7 | from __future__ import unicode_literals, print_function 8 | import yaml 9 | import json 10 | 11 | 12 | def main(): 13 | """ 14 | Write a Python program that creates a list. One of the elements of the list 15 | should be a dictionary with at least two keys. Write this list out to a file 16 | using both YAML and JSON formats. The YAML file should be in the expanded 17 | form. 18 | """ 19 | yaml_file = 'my_test.yml' 20 | json_file = 'my_test.json' 21 | 22 | my_dict = { 23 | 'ip_addr': '172.31.200.1', 24 | 'platform': 'cisco_ios', 25 | 'vendor': 'cisco', 26 | 'model': '1921' 27 | } 28 | 29 | my_list = [ 30 | 'some string', 31 | 99, 32 | 18, 33 | my_dict, 34 | 'another string', 35 | 'final string' 36 | ] 37 | 38 | with open(yaml_file, "w") as f: 39 | f.write(yaml.dump(my_list, default_flow_style=False)) 40 | 41 | with open(json_file, "w") as f: 42 | json.dump(my_list, f) 43 | 44 | 45 | if __name__ == "__main__": 46 | main() 47 | -------------------------------------------------------------------------------- /class1/ex7_yaml_json_read.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Write a Python program that reads both the YAML file and the JSON file created 4 | in exercise6 and pretty prints the data structure that is returned. 5 | """ 6 | from __future__ import unicode_literals, print_function 7 | import yaml 8 | import json 9 | from pprint import pprint 10 | 11 | 12 | def output_format(my_list, my_str): 13 | """Make the output format easier to read.""" 14 | print('\n\n') 15 | print('#' * 3) 16 | print('#' * 3 + my_str) 17 | print('#' * 3) 18 | pprint(my_list) 19 | 20 | 21 | def main(): 22 | """Read YAML and JSON files. Pretty print to standard out.""" 23 | yaml_file = 'my_test.yml' 24 | json_file = 'my_test.json' 25 | 26 | with open(yaml_file) as f: 27 | yaml_list = yaml.load(f) 28 | 29 | with open(json_file) as f: 30 | json_list = json.load(f) 31 | 32 | output_format(yaml_list, ' YAML') 33 | output_format(json_list, ' JSON') 34 | print('\n') 35 | 36 | 37 | if __name__ == "__main__": 38 | main() 39 | -------------------------------------------------------------------------------- /class1/ex8_confparse.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Write a Python program using ciscoconfparse that parses the 'cisco_ipsec.txt' 4 | config file. Note, this config file is not fully valid (i.e. parts of the 5 | configuration are missing). 6 | 7 | The script should find all of the crypto map entries in the file (lines that 8 | begin with 'crypto map CRYPTO') and print out the children of each crypto map. 9 | """ 10 | from __future__ import unicode_literals, print_function 11 | from ciscoconfparse import CiscoConfParse 12 | 13 | 14 | def main(): 15 | """ 16 | Find all of the crypto map entries in the file (lines that begin with 17 | 'crypto map CRYPTO') and print out the children of each crypto map. 18 | """ 19 | cisco_file = 'cisco_ipsec.txt' 20 | 21 | cisco_cfg = CiscoConfParse(cisco_file) 22 | crypto_maps = cisco_cfg.find_objects(r"^crypto map CRYPTO") 23 | 24 | for c_map in crypto_maps: 25 | print() 26 | print(c_map.text) 27 | for child in c_map.children: 28 | print(child.text) 29 | print() 30 | 31 | 32 | if __name__ == "__main__": 33 | main() 34 | -------------------------------------------------------------------------------- /class1/ex9_confparse.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Use the ciscoconfparse library to find the crypto maps that are using pfs group2 4 | """ 5 | from __future__ import unicode_literals, print_function 6 | from ciscoconfparse import CiscoConfParse 7 | 8 | 9 | def main(): 10 | """ 11 | Use the ciscoconfparse library to find the crypto maps that are using pfs 12 | group2 13 | """ 14 | cisco_file = 'cisco_ipsec.txt' 15 | 16 | cisco_cfg = CiscoConfParse(cisco_file) 17 | crypto_maps = cisco_cfg.find_objects_w_child(parentspec=r'crypto map CRYPTO', 18 | childspec=r'pfs group2') 19 | print("\nCrypto Maps using PFS group2:") 20 | for entry in crypto_maps: 21 | print(" {}".format(entry.text)) 22 | print() 23 | 24 | 25 | if __name__ == "__main__": 26 | main() 27 | -------------------------------------------------------------------------------- /class1/my_test.json: -------------------------------------------------------------------------------- 1 | ["some string", 99, 18, {"vendor": "cisco", "platform": "cisco_ios", "model": "1921", "ip_addr": "172.31.200.1"}, "another string", "final string"] -------------------------------------------------------------------------------- /class1/my_test.yml: -------------------------------------------------------------------------------- 1 | - !!python/unicode 'some string' 2 | - 99 3 | - 18 4 | - !!python/unicode 'ip_addr': !!python/unicode '172.31.200.1' 5 | !!python/unicode 'model': !!python/unicode '1921' 6 | !!python/unicode 'platform': !!python/unicode 'cisco_ios' 7 | !!python/unicode 'vendor': !!python/unicode 'cisco' 8 | - !!python/unicode 'another string' 9 | - !!python/unicode 'final string' 10 | -------------------------------------------------------------------------------- /class2/ex1b_libraries.txt: -------------------------------------------------------------------------------- 1 | 2 | $ python 3 | Python 3.6.2 (default, Feb 19 2018, 21:55:54) 4 | [GCC 4.8.5 20150623 (Red Hat 4.8.5-11)] on linux 5 | Type "help", "copyright", "credits" or "license" for more information. 6 | 7 | >>> import pysnmp 8 | >>> import paramiko 9 | 10 | >>> pysnmp.__version__ 11 | '4.4.4' 12 | 13 | >>> paramiko.__version__ 14 | '2.4.0' 15 | 16 | -------------------------------------------------------------------------------- /class2/ex1c_lib_path.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import my_func 4 | 5 | my_func.my_func() 6 | -------------------------------------------------------------------------------- /class2/ex1c_lib_path.txt: -------------------------------------------------------------------------------- 1 | 2 | ### my_func.py in the same directory as ex1c_lib_path.py 3 | [ class2]$ ./ex1c_lib_path.py 4 | hello 5 | 6 | 7 | 8 | ### Test with my_func.py in subdirectory test/ 9 | [ class2]$ mkdir test 10 | [ class2]$ mv my_func.py test/ 11 | 12 | # PY2 13 | [ class2]$ rm my_func.pyc 14 | # PY3 15 | [ class2]$ rm __pycache__/my_func.cpython-36.pyc 16 | 17 | [ class2]$ ./ex1c_lib_path.py 18 | Traceback (most recent call last): 19 | File "./ex1c_lib_path.py", line 3, in 20 | import my_func 21 | ImportError: No module named my_func 22 | 23 | [ class2]$ export PYTHONPATH=/home/kbyers/python_course/class2/test/ 24 | [ class2]$ ./ex1c_lib_path.py 25 | hello 26 | 27 | 28 | 29 | ### Test with my_func.py in 'site-packages' 30 | [ class2]$ unset PYTHONPATH 31 | [ class2]$ cd test/ 32 | 33 | # PY2 34 | [ test]$ ls 35 | my_func.py my_func.pyc 36 | 37 | [ test]$ mv my_func.py ~/VENV/py27_venv/lib/python2.7/site-packages/ 38 | 39 | # PY3 40 | [ test]$ ls 41 | my_func.py __pycache__ 42 | 43 | [ test]$ mv my_func.py ~/VENV/py3_venv/lib/python3.6/site-packages/ 44 | 45 | [ test]$ cd .. 46 | [ class2]$ rm -r test 47 | [ class2]$ ./ex1c_lib_path.py 48 | hello 49 | 50 | -------------------------------------------------------------------------------- /class2/ex2_telnetlib.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Write a script that connects to the lab pynet-rtr1, logins, and executes the 4 | 'show ip int brief' command. 5 | 6 | This program is meaningfully more complicated in PY3, than PY2. Also it 7 | is more complicated if you need both PY2 and PY3 compatibility. 8 | """ 9 | from __future__ import print_function, unicode_literals 10 | 11 | import telnetlib 12 | import time 13 | import socket 14 | import sys 15 | import getpass 16 | 17 | TELNET_PORT = 23 18 | TELNET_TIMEOUT = 6 19 | 20 | 21 | def write_bytes(out_data): 22 | """Write Python2 and Python3 compatible byte stream. 23 | 24 | This is a bit compliated :-( 25 | 26 | It basically ensures that the unicode in your program is always encoded into an UTF-8 byte 27 | stream in the proper way (when bytes are written out to the network). 28 | 29 | Or worded another way, Unicode in the program is in the idealized unicode code points, and 30 | when you write it out to the network it needs represented a certain way (encoded). 31 | """ 32 | if sys.version_info[0] >= 3: 33 | if isinstance(out_data, type(u'')): 34 | return out_data.encode('utf-8') 35 | elif isinstance(out_data, type(b'')): 36 | return out_data 37 | else: 38 | if isinstance(out_data, type(u'')): 39 | return out_data.encode('utf-8') 40 | elif isinstance(out_data, type(str(''))): 41 | return out_data 42 | msg = "Invalid value for out_data neither unicode nor byte string: {}".format(out_data) 43 | raise ValueError(msg) 44 | 45 | 46 | def write_channel(remote_conn, data): 47 | """Handle the PY2/PY3 differences to write data out to the device.""" 48 | remote_conn.write(write_bytes(data)) 49 | 50 | 51 | def read_channel(remote_conn): 52 | """Handle the PY2/PY3 differences to write data out to the device.""" 53 | return remote_conn.read_very_eager().decode('utf-8', 'ignore') 54 | 55 | 56 | def telnet_connect(ip_addr): 57 | """Establish telnet connection.""" 58 | try: 59 | return telnetlib.Telnet(ip_addr, TELNET_PORT, TELNET_TIMEOUT) 60 | except socket.timeout: 61 | sys.exit("Connection timed-out") 62 | 63 | 64 | def login(remote_conn, username, password): 65 | """Login to network device.""" 66 | output = remote_conn.read_until(b"sername:", TELNET_TIMEOUT).decode('utf-8', 'ignore') 67 | write_channel(remote_conn, username + '\n') 68 | output += remote_conn.read_until(b"ssword:", TELNET_TIMEOUT).decode('utf-8', 'ignore') 69 | write_channel(remote_conn, password + '\n') 70 | return output 71 | 72 | 73 | def disable_paging(remote_conn, paging_cmd='terminal length 0'): 74 | """Disable the paging of output (i.e. --More--).""" 75 | return send_command(remote_conn, paging_cmd) 76 | 77 | 78 | def send_command(remote_conn, cmd): 79 | """ 80 | Send a command down the telnet channel. 81 | 82 | Return the response 83 | """ 84 | cmd = cmd.rstrip() 85 | write_channel(remote_conn, cmd + '\n') 86 | time.sleep(1) 87 | return read_channel(remote_conn) 88 | 89 | 90 | def main(): 91 | """ 92 | Write a script that connects to the lab pynet-rtr1, logins, and executes the 93 | 'show ip int brief' command. 94 | """ 95 | try: 96 | ip_addr = raw_input("IP address: ") 97 | except NameError: 98 | ip_addr = input("IP address: ") 99 | ip_addr = ip_addr.strip() 100 | username = 'pyclass' 101 | password = getpass.getpass() 102 | 103 | remote_conn = telnet_connect(ip_addr) 104 | output = login(remote_conn, username, password) 105 | 106 | time.sleep(1) 107 | read_channel(remote_conn) 108 | disable_paging(remote_conn) 109 | 110 | output = send_command(remote_conn, 'show ip int brief') 111 | 112 | print("\n\n") 113 | print(output) 114 | print("\n\n") 115 | 116 | remote_conn.close() 117 | 118 | 119 | if __name__ == "__main__": 120 | main() 121 | -------------------------------------------------------------------------------- /class2/ex3_telnet_class.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Convert the code from exercise2 to a class-based solution 4 | 5 | This program is meaningfully more complicated in PY3, than PY2. Also it 6 | is more complicated if you need both PY2 and PY3 compatibility. 7 | """ 8 | from __future__ import print_function, unicode_literals 9 | 10 | import telnetlib 11 | import time 12 | import socket 13 | import sys 14 | import getpass 15 | 16 | TELNET_PORT = 23 17 | TELNET_TIMEOUT = 6 18 | 19 | 20 | def write_bytes(out_data): 21 | """Write Python2 and Python3 compatible byte stream. 22 | 23 | This is a bit compliated :-( 24 | 25 | It basically ensures that the unicode in your program is always encoded into an UTF-8 byte 26 | stream in the proper way (when bytes are written out to the network). 27 | 28 | Or worded another way, Unicode in the program is in the idealized unicode code points, and 29 | when you write it out to the network it needs represented a certain way (encoded). 30 | """ 31 | if sys.version_info[0] >= 3: 32 | if isinstance(out_data, type(u'')): 33 | return out_data.encode('utf-8') 34 | elif isinstance(out_data, type(b'')): 35 | return out_data 36 | else: 37 | if isinstance(out_data, type(u'')): 38 | return out_data.encode('utf-8') 39 | elif isinstance(out_data, type(str(''))): 40 | return out_data 41 | msg = "Invalid value for out_data neither unicode nor byte string: {}".format(out_data) 42 | raise ValueError(msg) 43 | 44 | 45 | class TelnetConn(object): 46 | """Establish and manage telnet connection to network devices.""" 47 | def __init__(self, ip_addr, username, password): 48 | self.ip_addr = ip_addr 49 | self.username = username 50 | self.password = password 51 | 52 | try: 53 | self.remote_conn = telnetlib.Telnet(self.ip_addr, TELNET_PORT, TELNET_TIMEOUT) 54 | except socket.timeout: 55 | sys.exit("Connection timed-out") 56 | 57 | def write_channel(self, data): 58 | """Handle the PY2/PY3 differences to write data out to the device.""" 59 | self.remote_conn.write(write_bytes(data)) 60 | 61 | def read_channel(self): 62 | """Handle the PY2/PY3 differences to write data out to the device.""" 63 | return self.remote_conn.read_very_eager().decode('utf-8', 'ignore') 64 | 65 | def login(self): 66 | """Login to network device.""" 67 | output = self.remote_conn.read_until(b"sername:", TELNET_TIMEOUT).decode('utf-8', 'ignore') 68 | self.write_channel(self.username + '\n') 69 | output += self.remote_conn.read_until(b"ssword:", TELNET_TIMEOUT).decode('utf-8', 'ignore') 70 | self.write_channel(self.password + '\n') 71 | time.sleep(1) 72 | return output 73 | 74 | def send_command(self, cmd="\n", sleep_time=1): 75 | """ 76 | Send a command down the telnet channel 77 | 78 | Return the response 79 | """ 80 | cmd = cmd.rstrip() 81 | self.write_channel(cmd + '\n') 82 | time.sleep(sleep_time) 83 | return self.read_channel() 84 | 85 | def disable_paging(self, paging_cmd='terminal length 0'): 86 | """Disable the paging of output.""" 87 | return self.send_command(paging_cmd) 88 | 89 | def close_conn(self): 90 | """Close telnet connection""" 91 | self.remote_conn.close() 92 | self.remote_conn = None 93 | 94 | 95 | def main(): 96 | """Convert the code from exercise2 to a class-based solution.""" 97 | try: 98 | ip_addr = raw_input("IP address: ") 99 | except NameError: 100 | ip_addr = input("IP address: ") 101 | ip_addr = ip_addr.strip() 102 | username = 'pyclass' 103 | password = getpass.getpass() 104 | 105 | my_conn = TelnetConn(ip_addr, username, password) 106 | my_conn.login() 107 | my_conn.send_command() 108 | my_conn.disable_paging() 109 | output = my_conn.send_command('show ip int brief') 110 | 111 | print("\n\n") 112 | print(output) 113 | print("\n\n") 114 | 115 | my_conn.close_conn() 116 | 117 | 118 | if __name__ == "__main__": 119 | main() 120 | -------------------------------------------------------------------------------- /class2/ex4_snmp.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Create a script that connects to both routers (pynet-rtr1 and pynet-rtr2) and 4 | prints out both the MIB2 sysName and sysDescr. 5 | """ 6 | from __future__ import print_function, unicode_literals 7 | import getpass 8 | import snmp_helper 9 | 10 | SYS_DESCR = '1.3.6.1.2.1.1.1.0' 11 | SYS_NAME = '1.3.6.1.2.1.1.5.0' 12 | 13 | 14 | def main(): 15 | """ 16 | Create a script that connects to both routers (pynet-rtr1 and pynet-rtr2) and 17 | prints out both the MIB2 sysName and sysDescr. 18 | """ 19 | try: 20 | ip_addr1 = raw_input("pynet-rtr1 IP address: ") 21 | ip_addr2 = raw_input("pynet-rtr2 IP address: ") 22 | except NameError: 23 | ip_addr1 = input("pynet-rtr1 IP address: ") 24 | ip_addr2 = input("pynet-rtr2 IP address: ") 25 | community_string = getpass.getpass(prompt="Community string: ") 26 | 27 | pynet_rtr1 = (ip_addr1, community_string, 161) 28 | pynet_rtr2 = (ip_addr2, community_string, 161) 29 | 30 | for a_device in (pynet_rtr1, pynet_rtr2): 31 | print("\n*********************") 32 | for the_oid in (SYS_NAME, SYS_DESCR): 33 | snmp_data = snmp_helper.snmp_get_oid(a_device, oid=the_oid) 34 | output = snmp_helper.snmp_extract(snmp_data) 35 | 36 | print(output) 37 | print("*********************") 38 | print() 39 | 40 | 41 | if __name__ == "__main__": 42 | main() 43 | -------------------------------------------------------------------------------- /class2/my_func.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | 3 | 4 | def my_func(): 5 | print("hello") 6 | -------------------------------------------------------------------------------- /class3/ex1_run_config_chg.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Using SNMPv3 create a script that detects router configuration changes. 4 | 5 | If the running configuration has changed, then send an email notification to 6 | yourself identifying the router that changed and the time that it changed. 7 | 8 | In this exercise, you will possibly need to save data to an external file. Use 9 | either JSON or YAML to save the data to an external file. 10 | """ 11 | from __future__ import print_function, unicode_literals 12 | 13 | try: 14 | # PY2 15 | import cPickle as pickle 16 | except ModuleNotFoundError: 17 | # PY3 18 | import pickle 19 | 20 | import os.path 21 | from datetime import datetime 22 | from getpass import getpass 23 | from collections import namedtuple 24 | 25 | import json 26 | import yaml 27 | 28 | from snmp_helper import snmp_get_oid_v3, snmp_extract 29 | from email_helper import send_mail 30 | 31 | 32 | # Create namedtuple for network devices 33 | NetworkDevice = namedtuple("NetworkDevice", "uptime last_changed run_config_changed") 34 | 35 | 36 | def obtain_saved_objects(file_name): 37 | """ 38 | Read in previously saved objects from a file 39 | 40 | Determine from the file_name whether .pkl, .yml, or .json, properly retrieve the data from 41 | the file. 42 | 43 | Return the retrieved network devices 44 | """ 45 | net_devices = {} 46 | 47 | # Check that the file exists 48 | if not os.path.isfile(file_name): 49 | return {} 50 | 51 | # Determine whether .pkl, .yml, or .json file 52 | if file_name.count(".") == 1: 53 | _, out_format = file_name.split(".") 54 | else: 55 | raise ValueError("Invalid file name: {0}".format(file_name)) 56 | 57 | if out_format == 'pkl': 58 | with open(file_name, 'rb') as f: 59 | while True: 60 | try: 61 | net_devices = pickle.load(f) 62 | except EOFError: 63 | break 64 | elif out_format == 'yml': 65 | with open(file_name, 'r') as f: 66 | net_devices = yaml.load(f) 67 | elif out_format == 'json': 68 | with open(file_name, 'r') as f: 69 | net_devices = json.load(f) 70 | 71 | # JSON returns straight tuple, convert to namedtuple 72 | for device_name, device_attrs in net_devices.items(): 73 | uptime, last_changed, run_config_changed = device_attrs 74 | tmp_device = NetworkDevice(uptime, last_changed, run_config_changed) 75 | net_devices[device_name] = tmp_device 76 | else: 77 | raise ValueError("Invalid file name: {}".format(file_name)) 78 | return net_devices 79 | 80 | 81 | def save_objects_to_file(file_name, data_dict): 82 | """Write the network devices out to a file.""" 83 | 84 | # Determine whether .pkl, .yml, or .json file 85 | if file_name.count(".") == 1: 86 | _, out_format = file_name.split(".") 87 | else: 88 | raise ValueError("Invalid file name: {}".format(file_name)) 89 | 90 | if out_format == 'pkl': 91 | with open(file_name, 'wb') as f: 92 | pickle.dump(data_dict, f) 93 | elif out_format == 'yml': 94 | with open(file_name, 'w') as f: 95 | f.write(yaml.dump(data_dict, default_flow_style=False)) 96 | elif out_format == 'json': 97 | with open(file_name, 'w') as f: 98 | json.dump(data_dict, f) 99 | 100 | 101 | def send_notification(device_name): 102 | """Send email notification regarding modified device.""" 103 | 104 | current_time = datetime.now() 105 | 106 | sender = 'sender@twb-tech.com' 107 | recipient = 'recipient@twb-tech.com' 108 | subject = 'Device {} was modified'.format(device_name) 109 | message = ''' 110 | The running configuration of {} was modified. 111 | 112 | This change was detected at: {} 113 | 114 | '''.format(device_name, current_time) 115 | 116 | if send_mail(recipient, subject, message, sender): 117 | print('Email notification sent to {}'.format(recipient)) 118 | return True 119 | 120 | 121 | def get_snmp_system_name(a_device, snmp_user): 122 | """Use SNMP to obtain the system name.""" 123 | sys_name_oid = '1.3.6.1.2.1.1.5.0' 124 | return snmp_extract(snmp_get_oid_v3(a_device, snmp_user, oid=sys_name_oid)) 125 | 126 | 127 | def get_snmp_uptime(a_device, snmp_user): 128 | """Use SNMP to obtain the system uptime.""" 129 | sys_uptime_oid = '1.3.6.1.2.1.1.3.0' 130 | return int(snmp_extract(snmp_get_oid_v3(a_device, snmp_user, oid=sys_uptime_oid))) 131 | 132 | 133 | def create_new_device(device_name, uptime, last_changed): 134 | """Create new Network Device.""" 135 | dots_to_print = (35 - len(device_name)) * '.' 136 | print("{} {}".format(device_name, dots_to_print), end=' ') 137 | print("saving new device") 138 | return NetworkDevice(uptime, last_changed, False) 139 | 140 | 141 | def check_for_reboot(saved_device, uptime, last_changed): 142 | """Check if the network devices has rebooted.""" 143 | # Did uptime decrease or did last_changed decrease 144 | return uptime < saved_device.uptime or last_changed < saved_device.last_changed 145 | 146 | 147 | def main(): 148 | """ 149 | Check if the running-configuration has changed, send an email notification when 150 | this occurs. 151 | 152 | Logic for detecting the running-config has changed: 153 | 154 | Normal (non-reboot): 155 | 156 | # Did RUN_LAST_CHANGED increase 157 | if RUN_LAST_CHANGED > network_device_object.last_changed: 158 | config_changed = True 159 | 160 | Reboot case: 161 | 162 | RUN_LAST_CHANGED decreases (i.e. < network_device_object.last_changed) 163 | 164 | Right after reboot, RUN_LAST_CHANGED is updated upon 165 | load of running-config from startup-config. 166 | 167 | Create a grace window (RELOAD_WINDOW) for values of RUN_LAST_CHANGED. 168 | In other words as long as RUN_LAST_CHANGED is <= RELOAD_WINDOW assume 169 | no running-config changes. 170 | 171 | If RUN_LAST_CHANGED is > RELOAD_WINDOW assume running-config was changed 172 | """ 173 | # 300 seconds (converted to hundredths of seconds) 174 | reload_window = 300 * 100 175 | 176 | # Uptime when running config last changed 177 | run_last_changed = '1.3.6.1.4.1.9.9.43.1.1.1.0' 178 | 179 | # File for storing previous RunningLastChanged timestamp 180 | net_dev_file = 'netdev.pkl' 181 | # net_dev_file = 'netdev.yml' 182 | # net_dev_file = 'netdev.json' 183 | 184 | try: 185 | rtr1_ip_addr = raw_input("Enter pynet-rtr1 IP: ") 186 | rtr2_ip_addr = raw_input("Enter pynet-rtr2 IP: ") 187 | except NameError: 188 | rtr1_ip_addr = input("Enter pynet-rtr1 IP: ") 189 | rtr2_ip_addr = input("Enter pynet-rtr2 IP: ") 190 | my_key = getpass(prompt="Auth + Encryption Key: ") 191 | 192 | # SNMPv3 Connection Parameters 193 | snmp_user = ('pysnmp', my_key, my_key) 194 | pynet_rtr1 = (rtr1_ip_addr, 161) 195 | pynet_rtr2 = (rtr2_ip_addr, 161) 196 | 197 | print('\n*** Checking for device changes ***') 198 | saved_devices = obtain_saved_objects(net_dev_file) 199 | print("{} devices were previously saved\n".format(len(saved_devices))) 200 | 201 | # Temporarily store the current devices in a dictionary 202 | current_devices = {} 203 | 204 | # Connect to each device / retrieve last_changed time 205 | for a_device in (pynet_rtr1, pynet_rtr2): 206 | device_name = get_snmp_system_name(a_device, snmp_user) 207 | uptime = get_snmp_uptime(a_device, snmp_user) 208 | last_changed = int(snmp_extract(snmp_get_oid_v3(a_device, snmp_user, oid=run_last_changed))) 209 | print("\nConnected to device = {}".format(device_name)) 210 | print("Last changed timestamp = {}".format(last_changed)) 211 | print("Uptime = {}".format(uptime)) 212 | 213 | # New network device 214 | if device_name not in saved_devices: 215 | current_devices[device_name] = create_new_device(device_name, uptime, last_changed) 216 | else: 217 | # Device has been previously saved 218 | saved_device = saved_devices[device_name] 219 | dots_to_print = (35 - len(device_name)) * '.' 220 | print("{} {}".format(device_name, dots_to_print), end=' ') 221 | 222 | if check_for_reboot(saved_device, uptime, last_changed): 223 | # reload_window is to try to ensure enough delay so that running-config will be 224 | # loaded upon reload. 225 | if last_changed <= reload_window: 226 | print("DEVICE RELOADED...not changed") 227 | current_devices[device_name] = NetworkDevice(uptime, last_changed, False) 228 | else: 229 | print("DEVICE RELOADED...and changed") 230 | current_devices[device_name] = NetworkDevice(uptime, last_changed, True) 231 | send_notification(device_name) 232 | 233 | # running-config did not change 234 | elif last_changed == saved_device.last_changed: 235 | print("not changed") 236 | current_devices[device_name] = NetworkDevice(uptime, last_changed, False) 237 | 238 | # running-config changed 239 | elif last_changed > saved_device.last_changed: 240 | print("CHANGED") 241 | current_devices[device_name] = NetworkDevice(uptime, last_changed, True) 242 | send_notification(device_name) 243 | else: 244 | raise ValueError() 245 | 246 | save_objects_to_file(net_dev_file, current_devices) 247 | print() 248 | 249 | 250 | if __name__ == '__main__': 251 | main() 252 | -------------------------------------------------------------------------------- /class3/ex2_snmp_int_graph.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Using SNMPv3 create two SVG image files. 4 | 5 | The first image file should graph the input and output octets on interface FA4 6 | on pynet-rtr1 every five minutes for an hour. Use the pygal library to create 7 | the SVG graph file. Note, you should be doing a subtraction here (i.e. the 8 | input/output octets transmitted during this five minute interval). 9 | 10 | The second SVG graph file should be the same as the first except graph the 11 | unicast packets received and transmitted. 12 | 13 | The relevant OIDs are as follows: 14 | 15 | ('ifDescr_fa4', '1.3.6.1.2.1.2.2.1.2.5') 16 | ('ifInOctets_fa4', '1.3.6.1.2.1.2.2.1.10.5') 17 | ('ifInUcastPkts_fa4', '1.3.6.1.2.1.2.2.1.11.5') 18 | ('ifOutOctets_fa4', '1.3.6.1.2.1.2.2.1.16.5'), 19 | ('ifOutUcastPkts_fa4', '1.3.6.1.2.1.2.2.1.17.5') 20 | """ 21 | from __future__ import print_function, unicode_literals 22 | 23 | from snmp_helper import snmp_get_oid_v3, snmp_extract 24 | import line_graph 25 | import time 26 | from getpass import getpass 27 | 28 | 29 | def get_interface_stats(snmp_device, snmp_user, stat_type, row_number): 30 | """ 31 | stat_type can be 'in_octets, out_octets, in_ucast_pkts, out_ucast_pkts 32 | 33 | returns the counter value as an integer 34 | """ 35 | 36 | oid_dict = { 37 | 'in_octets': '1.3.6.1.2.1.2.2.1.10', 38 | 'out_octets': '1.3.6.1.2.1.2.2.1.16', 39 | 'in_ucast_pkts': '1.3.6.1.2.1.2.2.1.11', 40 | 'out_ucast_pkts': '1.3.6.1.2.1.2.2.1.17', 41 | } 42 | 43 | if stat_type not in oid_dict.keys(): 44 | raise ValueError("Invalid value for stat_type: {}" % stat_type) 45 | 46 | # Make sure row_number can be converted to an int 47 | row_number = int(row_number) 48 | 49 | # Append row number to OID 50 | oid = oid_dict[stat_type] 51 | oid = oid + '.' + str(row_number) 52 | 53 | snmp_data = snmp_get_oid_v3(snmp_device, snmp_user, oid) 54 | return int(snmp_extract(snmp_data)) 55 | 56 | 57 | def create_graph(graph_stats, sample_duration): 58 | """Generate the graph files.""" 59 | 60 | print() 61 | x_labels = [] 62 | for x_label in range(1, 13): 63 | x_labels.append(str(x_label * sample_duration)) 64 | 65 | # Create the graphs 66 | if line_graph.twoline("pynet-rtr1-octets.svg", "pynet-rtr1 Fa4 Input/Output Bytes", 67 | graph_stats["in_octets"], "In Octets", graph_stats["out_octets"], 68 | "Out Octets", x_labels): 69 | print("In/Out Octets graph created") 70 | 71 | if line_graph.twoline("pynet-rtr1-pkts.svg", "pynet-rtr1 Fa4 Input/Output Unicast Packets", 72 | graph_stats["in_ucast_pkts"], "In Packets", graph_stats["out_ucast_pkts"], 73 | "Out Packets", x_labels): 74 | print("In/Out Packets graph created") 75 | print() 76 | 77 | 78 | def main(): 79 | """ 80 | Connect to router via SNMPv3. 81 | 82 | Create two graphs in/out octets and in/out packets 83 | """ 84 | try: 85 | rtr1_ip_addr = raw_input("Enter pynet-rtr1 IP: ") 86 | except NameError: 87 | rtr1_ip_addr = input("Enter pynet-rtr1 IP: ") 88 | my_key = getpass(prompt="Auth + Encryption Key: ") 89 | 90 | # SNMPv3 Connection Parameters 91 | a_user = 'pysnmp' 92 | auth_key = my_key 93 | encrypt_key = my_key 94 | 95 | snmp_user = (a_user, auth_key, encrypt_key) 96 | snmp_device = (rtr1_ip_addr, 161) 97 | 98 | # Fa4 is in row number5 in the MIB-2 interfaces table 99 | row_number = 5 100 | graph_stats = { 101 | "in_octets": [], 102 | "out_octets": [], 103 | "in_ucast_pkts": [], 104 | "out_ucast_pkts": [], 105 | } 106 | base_count_dict = {} 107 | 108 | # Enter a loop gathering SNMP data every 5 minutes for an hour. 109 | # 13 samples, one every 5 minutes 110 | SLEEP_TIME = 5 111 | for count in range(12): 112 | print() 113 | time_track = count * SLEEP_TIME 114 | print("{:>20} {:<60}".format("time", time_track)) 115 | 116 | # Gather SNMP statistics for each of octet/packets in and out 117 | for entry in graph_stats.keys(): 118 | snmp_retrieved_count = get_interface_stats(snmp_device, snmp_user, entry, row_number) 119 | # Base counter is a dictionary with the last sample value 120 | base_count = base_count_dict.get(entry) 121 | if base_count: 122 | # Calculate the difference from the last sample 123 | calculated_diff = snmp_retrieved_count - base_count 124 | # Save the data to graph_stats dictionary 125 | graph_stats[entry].append(calculated_diff) 126 | print("{:>20} {:<60}".format(entry, calculated_diff)) 127 | # Update the base counter value 128 | base_count_dict[entry] = snmp_retrieved_count 129 | time.sleep(SLEEP_TIME) 130 | 131 | # Create the graphs 132 | create_graph(graph_stats, sample_duration=SLEEP_TIME) 133 | 134 | 135 | if __name__ == '__main__': 136 | main() 137 | -------------------------------------------------------------------------------- /class3/line_graph.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | import pygal 3 | 4 | 5 | def twoline(file_name, title, line1, line1_label, line2, line2_label, x_labels): 6 | """ 7 | Line1 is a list of data points 8 | Line2 is a list of data points 9 | x_labels are labels that correspond to the data points in line1 and line2 10 | 11 | Example call: 12 | line_graph.twoline("pynet-rtr1-octets.svg", "pynet-rtr1 Fa4 Input/Output Bytes", 13 | in_octets_list, "In Octets", out_octets_list, "Out Octets", x_labels_list): 14 | """ 15 | line_chart = pygal.Line(include_x_axis=True) 16 | line_chart.title = title 17 | line_chart.x_labels = x_labels 18 | line_chart.add(line1_label, line1) 19 | line_chart.add(line2_label, line2) 20 | line_chart.render_to_file(file_name) 21 | return True 22 | -------------------------------------------------------------------------------- /class4/config_file.txt: -------------------------------------------------------------------------------- 1 | logging buffered 9990 2 | no logging console 3 | -------------------------------------------------------------------------------- /class4/ex1_paramiko.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Use Paramiko to retrieve the entire 'show version' output.""" 3 | from __future__ import print_function, unicode_literals 4 | import paramiko 5 | import time 6 | from getpass import getpass 7 | 8 | MAX_BUFFER = 65535 9 | 10 | 11 | def clear_buffer(remote_conn): 12 | """Clear any data in the receive buffer.""" 13 | if remote_conn.recv_ready(): 14 | return remote_conn.recv(MAX_BUFFER).decode('utf-8', 'ignore') 15 | 16 | 17 | def disable_paging(remote_conn, cmd='terminal length 0'): 18 | """Disable output paging (i.e. --More--).""" 19 | cmd = cmd.strip() 20 | remote_conn.send(cmd + '\n') 21 | time.sleep(1) 22 | clear_buffer(remote_conn) 23 | 24 | 25 | def send_command(remote_conn, cmd='', delay=1): 26 | """Send command down the channel. Retrieve and return the output.""" 27 | if cmd != '': 28 | cmd = cmd.strip() 29 | remote_conn.send(cmd + '\n') 30 | time.sleep(delay) 31 | 32 | if remote_conn.recv_ready(): 33 | return remote_conn.recv(MAX_BUFFER).decode('utf-8', 'ignore') 34 | else: 35 | return '' 36 | 37 | 38 | def main(): 39 | """Use Paramiko to retrieve the entire 'show version' output.""" 40 | try: 41 | ip_addr = raw_input("Enter IP address: ") 42 | except NameError: 43 | ip_addr = input("Enter IP address: ") 44 | 45 | username = 'pyclass' 46 | password = getpass() 47 | port = 22 48 | 49 | remote_conn_pre = paramiko.SSHClient() 50 | remote_conn_pre.load_system_host_keys() 51 | 52 | remote_conn_pre.connect(ip_addr, port=port, username=username, password=password, 53 | look_for_keys=False, allow_agent=False) 54 | remote_conn = remote_conn_pre.invoke_shell() 55 | 56 | time.sleep(1) 57 | clear_buffer(remote_conn) 58 | disable_paging(remote_conn) 59 | 60 | output = send_command(remote_conn, cmd='show version') 61 | print('\n>>>>') 62 | print(output) 63 | print('>>>>\n') 64 | 65 | 66 | if __name__ == "__main__": 67 | main() 68 | -------------------------------------------------------------------------------- /class4/ex2_paramiko.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Use Paramiko to change 'logging buffered ' configuration.""" 3 | from __future__ import print_function, unicode_literals 4 | 5 | import paramiko 6 | import time 7 | from getpass import getpass 8 | 9 | MAX_BUFFER = 65535 10 | 11 | 12 | def clear_buffer(remote_conn): 13 | """Clear any data in the receive buffer.""" 14 | if remote_conn.recv_ready(): 15 | return remote_conn.recv(MAX_BUFFER).decode('utf-8', 'ignore') 16 | 17 | 18 | def disable_paging(remote_conn, cmd='terminal length 0'): 19 | """Disable output paging (i.e. --More--).""" 20 | cmd = cmd.strip() 21 | remote_conn.send(cmd + '\n') 22 | time.sleep(1) 23 | clear_buffer(remote_conn) 24 | 25 | 26 | def send_command(remote_conn, cmd='', delay=1): 27 | """Send command down the channel. Retrieve and return the output.""" 28 | if cmd != '': 29 | cmd = cmd.strip() 30 | remote_conn.send(cmd + '\n') 31 | time.sleep(delay) 32 | 33 | if remote_conn.recv_ready(): 34 | return remote_conn.recv(MAX_BUFFER).decode('utf-8', 'ignore') 35 | else: 36 | return '' 37 | 38 | 39 | def main(): 40 | """Use Paramiko to change 'logging buffered ' configuration.""" 41 | try: 42 | ip_addr = raw_input("Enter IP address: ") 43 | except NameError: 44 | ip_addr = input("Enter IP address: ") 45 | username = 'pyclass' 46 | password = getpass() 47 | port = 22 48 | 49 | remote_conn_pre = paramiko.SSHClient() 50 | remote_conn_pre.load_system_host_keys() 51 | 52 | remote_conn_pre.connect(ip_addr, port=port, username=username, password=password, 53 | look_for_keys=False, allow_agent=False) 54 | remote_conn = remote_conn_pre.invoke_shell() 55 | 56 | time.sleep(1) 57 | clear_buffer(remote_conn) 58 | disable_paging(remote_conn) 59 | 60 | send_command(remote_conn, cmd='conf t') 61 | send_command(remote_conn, cmd='logging buffered 20010') 62 | send_command(remote_conn, cmd='end') 63 | 64 | output = send_command(remote_conn, cmd='show run | inc logging', delay=2) 65 | print('\n>>>>') 66 | print(output) 67 | print('>>>>\n') 68 | 69 | 70 | if __name__ == "__main__": 71 | main() 72 | -------------------------------------------------------------------------------- /class4/ex3_pexpect.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Use Pexpect to retrieve the output of 'show ip int brief'.""" 3 | from __future__ import print_function, unicode_literals 4 | import pexpect 5 | import time 6 | from getpass import getpass 7 | 8 | 9 | def login(ssh_conn): 10 | """Handle sending password.""" 11 | password = getpass() 12 | 13 | ssh_conn.expect('ssword:') 14 | ssh_conn.sendline(password) 15 | ssh_conn.expect('#') 16 | 17 | 18 | def find_prompt(ssh_conn): 19 | """ 20 | Find the current prompt 21 | 22 | Pexpect is non-greedy which is problematic 23 | """ 24 | ssh_conn.send('\n') 25 | time.sleep(1) 26 | ssh_conn.expect('#') 27 | prompt = ssh_conn.before + ssh_conn.after 28 | return prompt.strip() 29 | 30 | 31 | def main(): 32 | """Use Pexpect to retrieve the output of 'show ip int brief'.""" 33 | try: 34 | ip_addr = raw_input("Enter IP address: ") 35 | except NameError: 36 | ip_addr = input("Enter IP address: ") 37 | username = 'pyclass' 38 | port = 22 39 | 40 | ssh_conn = pexpect.spawn('ssh -l {} {} -p {}'.format(username, ip_addr, port)) 41 | ssh_conn.timeout = 3 42 | 43 | login(ssh_conn) 44 | prompt = find_prompt(ssh_conn) 45 | 46 | ssh_conn.sendline('terminal length 0') 47 | ssh_conn.expect(prompt) 48 | 49 | ssh_conn.sendline('show ip int brief') 50 | ssh_conn.expect(prompt) 51 | 52 | print('\n>>>>') 53 | print(ssh_conn.before.decode('utf-8', 'ignore')) 54 | print('>>>>\n') 55 | 56 | 57 | if __name__ == "__main__": 58 | main() 59 | -------------------------------------------------------------------------------- /class4/ex4_pexpect.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Use Pexpect to change the logging buffer size (logging buffered ). 4 | 5 | Verify this change by examining the output of 'show run'. 6 | """ 7 | from __future__ import print_function, unicode_literals 8 | 9 | import pexpect 10 | import time 11 | from getpass import getpass 12 | 13 | 14 | def login(ssh_conn): 15 | """Handle sending password.""" 16 | password = getpass() 17 | 18 | ssh_conn.expect('ssword:') 19 | ssh_conn.sendline(password) 20 | ssh_conn.expect('#') 21 | 22 | 23 | def find_prompt(ssh_conn): 24 | """ 25 | Find the current prompt 26 | 27 | Pexpect is non-greedy which is problematic 28 | """ 29 | ssh_conn.send('\n') 30 | time.sleep(1) 31 | ssh_conn.expect('#') 32 | prompt = ssh_conn.before + ssh_conn.after 33 | return prompt.strip() 34 | 35 | 36 | def disable_paging(ssh_conn, pattern='#', cmd='terminal length 0'): 37 | """Disable the paging of output i.e. --More--.""" 38 | ssh_conn.sendline(cmd) 39 | ssh_conn.expect(pattern) 40 | 41 | 42 | def main(): 43 | """ 44 | Use Pexpect to change the logging buffer size (logging buffered ). 45 | 46 | Verify this change by examining the output of 'show run'. 47 | """ 48 | try: 49 | ip_addr = raw_input("Enter IP address: ") 50 | except NameError: 51 | ip_addr = input("Enter IP address: ") 52 | username = 'pyclass' 53 | port = 22 54 | 55 | ssh_conn = pexpect.spawn('ssh -l {} {} -p {}'.format(username, ip_addr, port)) 56 | ssh_conn.timeout = 3 57 | 58 | login(ssh_conn) 59 | prompt = find_prompt(ssh_conn) 60 | disable_paging(ssh_conn, prompt) 61 | 62 | ssh_conn.sendline('conf t') 63 | ssh_conn.expect('#') 64 | 65 | ssh_conn.sendline('logging buffer 9000') 66 | ssh_conn.expect('#') 67 | 68 | ssh_conn.sendline('end') 69 | ssh_conn.expect(prompt) 70 | 71 | ssh_conn.sendline('show run | inc logging buffer') 72 | ssh_conn.expect(prompt) 73 | 74 | print('\n>>>>') 75 | print(ssh_conn.before.decode('utf-8', 'ignore')) 76 | print('>>>>\n') 77 | 78 | 79 | if __name__ == "__main__": 80 | main() 81 | -------------------------------------------------------------------------------- /class4/ex5_netmiko.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Using Netmiko enter into configuration mode on a network device. 4 | 5 | Verify that you are currently in configuration mode. 6 | """ 7 | from __future__ import print_function, unicode_literals 8 | from getpass import getpass 9 | from netmiko import ConnectHandler 10 | from test_devices import pynet1, pynet2, juniper_srx 11 | 12 | 13 | def main(): 14 | """ 15 | Using Netmiko enter into configuration mode on a network device. 16 | 17 | Verify that you are currently in configuration mode. 18 | """ 19 | password = getpass() 20 | 21 | for a_dict in (pynet1, pynet2, juniper_srx): 22 | a_dict['password'] = password 23 | net_connect2 = ConnectHandler(**pynet2) 24 | net_connect2.config_mode() 25 | print("\n>>>>") 26 | print("Checking pynet-rtr2 is in configuration mode.") 27 | print("Config mode check: {}".format(net_connect2.check_config_mode())) 28 | print("Current prompt: {}".format(net_connect2.find_prompt())) 29 | print(">>>>\n") 30 | 31 | 32 | if __name__ == "__main__": 33 | main() 34 | -------------------------------------------------------------------------------- /class4/ex6_netmiko.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Use Netmiko to execute 'show arp' on pynet-rtr1, pynet-rtr2, and juniper-srx.""" 3 | from __future__ import print_function, unicode_literals 4 | 5 | from getpass import getpass 6 | from datetime import datetime 7 | from netmiko import ConnectHandler 8 | from test_devices import pynet1, pynet2, juniper_srx 9 | 10 | 11 | def main(): 12 | """Use Netmiko to execute 'show arp' on pynet-rtr1, pynet-rtr2, and juniper-srx.""" 13 | password = getpass() 14 | 15 | # Get connection parameters setup correctly 16 | for a_dict in (pynet1, pynet2, juniper_srx): 17 | a_dict['password'] = password 18 | a_dict['verbose'] = False 19 | 20 | print("\nStart time: " + str(datetime.now())) 21 | for a_device in (pynet1, pynet2, juniper_srx): 22 | net_connect = ConnectHandler(**a_device) 23 | output = net_connect.send_command("show arp") 24 | print() 25 | print('#' * 80) 26 | print("Device: {}:{}".format(net_connect.ip, net_connect.port)) 27 | print() 28 | print(output) 29 | print('#' * 80) 30 | print() 31 | 32 | print("\nEnd time: " + str(datetime.now())) 33 | 34 | 35 | if __name__ == "__main__": 36 | main() 37 | -------------------------------------------------------------------------------- /class4/ex7_netmiko.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Use Netmiko to change the logging buffer size on pynet-rtr2.""" 3 | from __future__ import print_function, unicode_literals 4 | 5 | from getpass import getpass 6 | from netmiko import ConnectHandler 7 | from test_devices import pynet1, pynet2, juniper_srx 8 | 9 | 10 | def main(): 11 | """Use Netmiko to change the logging buffer size on pynet-rtr2.""" 12 | password = getpass() 13 | 14 | # Get connection parameters setup correctly 15 | for a_dict in (pynet1, pynet2, juniper_srx): 16 | a_dict['password'] = password 17 | a_dict['verbose'] = False 18 | 19 | net_connect = ConnectHandler(**pynet2) 20 | config_commands = ['logging buffered 20000'] 21 | net_connect.send_config_set(config_commands) 22 | 23 | output = net_connect.send_command("show run | inc logging buffer") 24 | print() 25 | print('#' * 80) 26 | print("Device: {}:{}".format(net_connect.ip, net_connect.port)) 27 | print() 28 | print(output) 29 | print('#' * 80) 30 | print() 31 | 32 | 33 | if __name__ == "__main__": 34 | main() 35 | -------------------------------------------------------------------------------- /class4/ex8_netmiko.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Use Netmiko to change the logging buffer size and to disable console logging 4 | from a file for both pynet-rtr1 and pynet-rtr2 5 | 6 | logging buffered 7 | no logging console 8 | """ 9 | from __future__ import print_function, unicode_literals 10 | 11 | from getpass import getpass 12 | from netmiko import ConnectHandler 13 | from test_devices import pynet1, pynet2, juniper_srx 14 | 15 | 16 | def main(): 17 | """ 18 | Use Netmiko to change the logging buffer size and to disable console logging 19 | from a file for both pynet-rtr1 and pynet-rtr2 20 | """ 21 | password = getpass() 22 | 23 | # Get connection parameters setup correctly 24 | for a_dict in (pynet1, pynet2, juniper_srx): 25 | a_dict['password'] = password 26 | a_dict['verbose'] = False 27 | 28 | for a_device in (pynet1, pynet2): 29 | net_connect = ConnectHandler(**a_device) 30 | net_connect.send_config_from_file(config_file='config_file.txt') 31 | 32 | # Verify configuration 33 | output = net_connect.send_command("show run | inc logging") 34 | print() 35 | print('#' * 80) 36 | print("Device: {}:{}".format(net_connect.ip, net_connect.port)) 37 | print() 38 | print(output) 39 | print('#' * 80) 40 | print() 41 | 42 | 43 | if __name__ == "__main__": 44 | main() 45 | -------------------------------------------------------------------------------- /class4/exercise_notes.txt: -------------------------------------------------------------------------------- 1 | ansible-playbook exercise2a.yml 2 | ansible-playbook exercise2b.yml 3 | ansible-playbook exercise3.yml 4 | ansible-playbook exercise4a.yml -i ./ansible-hosts-4a 5 | ansible-playbook exercise4b.yml 6 | ansible-playbook exercise5.yml 7 | 8 | Note, exercise 4a should fail (i.e. we are testing that it fails in this case). 9 | -------------------------------------------------------------------------------- /class4/test_devices.py: -------------------------------------------------------------------------------- 1 | pynet1 = { 2 | 'device_type': 'cisco_ios', 3 | 'ip': '184.105.247.70', 4 | 'username': 'pyclass', 5 | } 6 | 7 | pynet2 = { 8 | 'device_type': 'cisco_ios', 9 | 'ip': '184.105.247.71', 10 | 'username': 'pyclass', 11 | 'secret': '', 12 | 'port': 22, 13 | } 14 | 15 | juniper_srx = { 16 | 'device_type': 'juniper', 17 | 'ip': '184.105.247.76', 18 | 'username': 'pyclass', 19 | 'port': 22, 20 | } 21 | -------------------------------------------------------------------------------- /class5/eapi_vlan.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Using Arista's pyeapi, create a script that allows you to add a VLAN (both the 4 | VLAN ID and the VLAN name). Your script should first check that the VLAN ID is 5 | available and only add the VLAN if it doesn't already exist. Use VLAN IDs 6 | between 100 and 999. You should be able to call the script from the command 7 | line as follows: 8 | 9 | python eapi_vlan.py --name blue 100 # add VLAN100, name blue 10 | 11 | If you call the script with the --remove option, the VLAN will be removed. 12 | 13 | python eapi_vlan.py --remove 100 # remove VLAN100 14 | 15 | Once again only remove the VLAN if it exists on the switch. You will probably 16 | want to use Python's argparse to accomplish the argument processing. 17 | """ 18 | from __future__ import unicode_literals, print_function 19 | import pyeapi 20 | import argparse 21 | import six 22 | 23 | 24 | def pyeapi_result(output): 25 | """Return the 'result' value from the pyeapi output.""" 26 | return output[0]['result'] 27 | 28 | 29 | def check_vlan_exists(eapi_conn, vlan_id): 30 | """ 31 | Check if the given VLAN exists 32 | 33 | Return either vlan_name or False 34 | """ 35 | vlan_id = six.text_type(vlan_id) 36 | cmd = 'show vlan id {}'.format(vlan_id) 37 | try: 38 | response = eapi_conn.enable(cmd) 39 | check_vlan = pyeapi_result(response)['vlans'] 40 | return check_vlan[vlan_id]['name'] 41 | except (pyeapi.eapilib.CommandError, KeyError): 42 | pass 43 | return False 44 | 45 | 46 | def configure_vlan(eapi_conn, vlan_id, vlan_name=None): 47 | """ 48 | Add the given vlan_id to the switch 49 | 50 | Set the vlan_name (if provided) 51 | 52 | Note, if the vlan already exists, then this will just set the vlan_name 53 | """ 54 | command_str1 = 'vlan {}'.format(vlan_id) 55 | cmd = [command_str1] 56 | if vlan_name is not None: 57 | command_str2 = 'name {}'.format(vlan_name) 58 | cmd.append(command_str2) 59 | return eapi_conn.config(cmd) 60 | 61 | 62 | def main(): 63 | """Add/remove vlans from Arista switch in an idempotent manner.""" 64 | eapi_conn = pyeapi.connect_to("pynet-sw2") 65 | 66 | # Argument parsing 67 | parser = argparse.ArgumentParser( 68 | description="Idempotent addition/removal of VLAN to Arista switch" 69 | ) 70 | parser.add_argument("vlan_id", help="VLAN number to create or remove", action="store", type=int) 71 | parser.add_argument( 72 | "--name", 73 | help="Specify VLAN name", 74 | action="store", 75 | dest="vlan_name", 76 | type=str 77 | ) 78 | parser.add_argument("--remove", help="Remove the given VLAN ID", action="store_true") 79 | 80 | cli_args = parser.parse_args() 81 | vlan_id = cli_args.vlan_id 82 | remove = cli_args.remove 83 | vlan_name = six.text_type(cli_args.vlan_name) 84 | 85 | # Check if VLAN already exists 86 | check_vlan = check_vlan_exists(eapi_conn, vlan_id) 87 | 88 | # check if action is remove or add 89 | if remove: 90 | if check_vlan: 91 | print("VLAN exists, removing it") 92 | command_str = 'no vlan {}'.format(vlan_id) 93 | eapi_conn.config([command_str]) 94 | else: 95 | print("VLAN does not exist, no action required") 96 | else: 97 | if check_vlan: 98 | if vlan_name is not None and check_vlan != vlan_name: 99 | print("VLAN already exists, setting VLAN name") 100 | configure_vlan(eapi_conn, vlan_id, vlan_name) 101 | else: 102 | print("VLAN already exists, no action required") 103 | else: 104 | print("Adding VLAN including vlan_name (if present)") 105 | configure_vlan(eapi_conn, vlan_id, vlan_name) 106 | 107 | 108 | if __name__ == "__main__": 109 | main() 110 | -------------------------------------------------------------------------------- /class5/ex1_eapi_interfaces.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Use Arista's eAPI to obtain 'show interfaces' from the switch. Parse the 'show 4 | interfaces' output to obtain the 'inOctets' and 'outOctets' fields for each of 5 | the interfaces on the switch. Accomplish this using Arista's pyeapi library. 6 | """ 7 | from __future__ import print_function, unicode_literals 8 | import pyeapi 9 | import six 10 | 11 | 12 | def pyeapi_result(output): 13 | """Return the 'result' value from the pyeapi output.""" 14 | return output[0]['result'] 15 | 16 | 17 | def main(): 18 | """Use Arista's eAPI to obtain 'show interfaces' from the switch.""" 19 | eapi_conn = pyeapi.connect_to("pynet-sw2") 20 | 21 | interfaces = eapi_conn.enable("show interfaces") 22 | interfaces = pyeapi_result(interfaces) 23 | 24 | # Strip off unneeded dictionary 25 | interfaces = interfaces['interfaces'] 26 | 27 | # inOctets/outOctets are fields inside 'interfaceCounters' dict 28 | data_stats = {} 29 | for interface, int_values in interfaces.items(): 30 | int_counters = int_values.get('interfaceCounters', {}) 31 | data_stats[interface] = (int_counters.get('inOctets'), int_counters.get('outOctets')) 32 | 33 | # Print output data 34 | print("\n{:20} {:<20} {:<20}".format("Interface:", "inOctets", "outOctets")) 35 | for intf, octets in sorted(data_stats.items()): 36 | print("{:20} {:<20} {:<20}".format(intf, six.text_type(octets[0]), 37 | six.text_type(octets[1]))) 38 | 39 | print() 40 | 41 | 42 | if __name__ == '__main__': 43 | main() 44 | -------------------------------------------------------------------------------- /class6/collateral/jinja2/aaa_template.j2: -------------------------------------------------------------------------------- 1 | aaa new-model 2 | ! 3 | ! 4 | aaa authentication login default local 5 | aaa authorization exec default local if-authenticated 6 | ! 7 | ! 8 | ! 9 | ! 10 | ! 11 | aaa session-id common 12 | -------------------------------------------------------------------------------- /class6/collateral/jinja2/bgp_ipv4_routes.j2: -------------------------------------------------------------------------------- 1 | address-family ipv4 unicast 2 | network {{ advertised_route1 }} 3 | network {{ advertised_route2 }} 4 | network {{ advertised_route3 }} 5 | -------------------------------------------------------------------------------- /class6/collateral/jinja2/bgp_peer_ipv4.j2: -------------------------------------------------------------------------------- 1 | 2 | neighbor {{ peer1_ip }} remote-as {{ peer1_as }} 3 | update-source loopback1 4 | ebgp-multihop 2 5 | address-family ipv4 unicast 6 | -------------------------------------------------------------------------------- /class6/collateral/jinja2/bgp_peer_ipv6.j2: -------------------------------------------------------------------------------- 1 | 2 | neighbor {{ peer1_ip }} remote-as {{ peer1_as }} 3 | update-source loopback1 4 | ebgp-multihop 2 5 | address-family ipv4 unicast 6 | address-family ipv6 unicast 7 | -------------------------------------------------------------------------------- /class6/collateral/jinja2/bgp_template.j2: -------------------------------------------------------------------------------- 1 | router bgp 42 2 | bgp router-id 10.220.88.20 3 | bgp log-neighbor-changes 4 | neighbor 10.220.88.38 remote-as 44 5 | -------------------------------------------------------------------------------- /class6/collateral/jinja2/cisco1_config.j2: -------------------------------------------------------------------------------- 1 | ! 2 | ! Last configuration change at 09:57:51 PDT Wed Apr 18 2018 by pyclass 3 | ! NVRAM config last updated at 14:55:08 PDT Wed Apr 18 2018 by pyclass 4 | ! 5 | version 15.4 6 | no service pad 7 | service timestamps debug datetime msec localtime show-timezone 8 | service timestamps log datetime msec localtime show-timezone 9 | no service password-encryption 10 | ! 11 | hostname pynet-rtr1 12 | ! 13 | boot-start-marker 14 | boot system flash c880data-universalk9-mz.154-2.T1.bin 15 | boot-end-marker 16 | ! 17 | ! 18 | logging buffered 10000 19 | no logging console 20 | enable secret 5 $1$39ck$xxxZ2sWVn17YCXhSWa48R/ 21 | ! 22 | aaa new-model 23 | ! 24 | ! 25 | aaa authentication login default local 26 | aaa authorization exec default local if-authenticated 27 | ! 28 | ! 29 | ! 30 | ! 31 | ! 32 | aaa session-id common 33 | memory-size iomem 10 34 | ! 35 | ! 36 | ! 37 | ! 38 | ! 39 | ! 40 | ! 41 | ! 42 | no ip domain lookup 43 | ip domain name twb-tech.com 44 | ip cef 45 | no ipv6 cef 46 | ! 47 | ! 48 | multilink bundle-name authenticated 49 | license udi pid CISCO881-SEC-K9 sn FTX1000001X 50 | ! 51 | ! 52 | archive 53 | path flash:pynet-rtr1-cfg 54 | file prompt quiet 55 | username pyclass privilege 15 secret 5 $1$DAXe$pxxnB1jFHAQAWt/1eXub21 56 | username testuser privilege 15 secret 5 $1$B5cD$5XACwB/8iwj2U 57 | username test9 secret 5 $1$CQCe$paanX1jABCQAWt/7eXyb21 58 | ! 59 | ! 60 | ! 61 | ! 62 | lldp run 63 | ! 64 | ip ssh version 2 65 | ip scp server enable 66 | ! 67 | ! 68 | ! 69 | ! 70 | ! 71 | ! 72 | ! 73 | interface FastEthernet0 74 | no ip address 75 | ! 76 | interface FastEthernet1 77 | no ip address 78 | ! 79 | interface FastEthernet2 80 | no ip address 81 | ! 82 | interface FastEthernet3 83 | no ip address 84 | ! 85 | interface FastEthernet4 86 | description *** LAN connection (don't change) *** 87 | ip address 10.220.88.20 255.255.255.0 88 | duplex auto 89 | speed auto 90 | ! 91 | interface Vlan1 92 | no ip address 93 | ! 94 | router bgp 42 95 | bgp router-id 10.220.88.20 96 | bgp log-neighbor-changes 97 | neighbor 10.220.88.38 remote-as 44 98 | ! 99 | ip forward-protocol nd 100 | no ip http server 101 | no ip http secure-server 102 | ! 103 | ! 104 | ip route 0.0.0.0 0.0.0.0 10.220.88.1 105 | ! 106 | ip access-list extended VPN-TEST1 107 | ip access-list extended VPN-TEST2 108 | ip access-list extended VPN-TEST3 109 | ip access-list extended VPN-TEST4 110 | ip access-list extended VPN-TEST5 111 | ! 112 | ! 113 | snmp-server group READONLY v3 priv read VIEWSTD access 98 114 | snmp-server view VIEWSTD iso included 115 | snmp-server community invalid RO 98 116 | snmp-server ifindex persist 117 | access-list 98 remark *** SNMP *** 118 | access-list 98 permit any 119 | ! 120 | ! 121 | ! 122 | control-plane 123 | ! 124 | ! 125 | no vstack 126 | ! 127 | line con 0 128 | logging synchronous 129 | no modem enable 130 | line aux 0 131 | line vty 0 4 132 | exec-timeout 20 0 133 | transport input telnet ssh 134 | ! 135 | scheduler max-task-time 5000 136 | ntp server 130.126.24.24 137 | ntp server 152.2.21.1 138 | onep 139 | transport type tls localcert TP-self-signed-1429897839 disable-remotecert-validation 140 | service set vty 141 | ! 142 | end 143 | -------------------------------------------------------------------------------- /class6/collateral/jinja2/cisco1_config_2.j2: -------------------------------------------------------------------------------- 1 | ! 2 | ! Last configuration change at 09:57:51 PDT Wed Apr 18 2018 by pyclass 3 | ! NVRAM config last updated at 14:55:08 PDT Wed Apr 18 2018 by pyclass 4 | ! 5 | version 15.4 6 | no service pad 7 | service timestamps debug datetime msec localtime show-timezone 8 | service timestamps log datetime msec localtime show-timezone 9 | no service password-encryption 10 | ! 11 | hostname pynet-rtr1 12 | ! 13 | boot-start-marker 14 | boot system flash c880data-universalk9-mz.154-2.T1.bin 15 | boot-end-marker 16 | ! 17 | ! 18 | logging buffered 10000 19 | no logging console 20 | enable secret 5 $1$39ck$xxxZ2sWVn17YCXhSWa48R/ 21 | ! 22 | {% include 'aaa_template.j2' %} 23 | memory-size iomem 10 24 | ! 25 | ! 26 | ! 27 | ! 28 | ! 29 | ! 30 | ! 31 | ! 32 | no ip domain lookup 33 | ip domain name twb-tech.com 34 | ip cef 35 | no ipv6 cef 36 | ! 37 | ! 38 | multilink bundle-name authenticated 39 | license udi pid CISCO881-SEC-K9 sn FTX1000001X 40 | ! 41 | ! 42 | archive 43 | path flash:pynet-rtr1-cfg 44 | file prompt quiet 45 | username pyclass privilege 15 secret 5 $1$DAXe$pxxnB1jFHAQAWt/1eXub21 46 | username testuser privilege 15 secret 5 $1$B5cD$5XACwB/8iwj2U 47 | username test9 secret 5 $1$CQCe$paanX1jABCQAWt/7eXyb21 48 | ! 49 | ! 50 | ! 51 | ! 52 | lldp run 53 | ! 54 | ip ssh version 2 55 | ip scp server enable 56 | ! 57 | ! 58 | ! 59 | ! 60 | ! 61 | ! 62 | ! 63 | interface FastEthernet0 64 | no ip address 65 | ! 66 | interface FastEthernet1 67 | no ip address 68 | ! 69 | interface FastEthernet2 70 | no ip address 71 | ! 72 | interface FastEthernet3 73 | no ip address 74 | ! 75 | interface FastEthernet4 76 | description *** LAN connection (don't change) *** 77 | ip address 10.220.88.20 255.255.255.0 78 | duplex auto 79 | speed auto 80 | ! 81 | interface Vlan1 82 | no ip address 83 | ! 84 | {% include 'bgp_template.j2' %} 85 | ! 86 | ip forward-protocol nd 87 | no ip http server 88 | no ip http secure-server 89 | ! 90 | ! 91 | ip route 0.0.0.0 0.0.0.0 10.220.88.1 92 | ! 93 | ip access-list extended VPN-TEST1 94 | ip access-list extended VPN-TEST2 95 | ip access-list extended VPN-TEST3 96 | ip access-list extended VPN-TEST4 97 | ip access-list extended VPN-TEST5 98 | ! 99 | ! 100 | {% include 'snmp_template.j2' %} 101 | ! 102 | ! 103 | ! 104 | control-plane 105 | ! 106 | ! 107 | no vstack 108 | ! 109 | line con 0 110 | logging synchronous 111 | no modem enable 112 | line aux 0 113 | line vty 0 4 114 | exec-timeout 20 0 115 | transport input telnet ssh 116 | ! 117 | scheduler max-task-time 5000 118 | ntp server 130.126.24.24 119 | ntp server 152.2.21.1 120 | onep 121 | transport type tls localcert TP-self-signed-1429897839 disable-remotecert-validation 122 | service set vty 123 | ! 124 | end 125 | -------------------------------------------------------------------------------- /class6/collateral/jinja2/intf_config.j2: -------------------------------------------------------------------------------- 1 | interface FastEthernet4 2 | description *** LAN connection (don't change) *** 3 | ip address {{ ip_addr }} {{ netmask }} 4 | duplex auto 5 | speed auto 6 | -------------------------------------------------------------------------------- /class6/collateral/jinja2/intf_config2.j2: -------------------------------------------------------------------------------- 1 | interface FastEthernet4 2 | description *** LAN connection (don't change) *** 3 | ip address {{ ip_addr }} {{ netmask }} 4 | duplex auto 5 | speed auto 6 | ! 7 | {%- if vlan1 == True %} 8 | interface Vlan1 9 | description *** Test description *** 10 | ip address {{ vlan1_ip_addr }} {{ vlan1_netmask }} 11 | duplex auto 12 | speed auto 13 | {%- endif %} 14 | -------------------------------------------------------------------------------- /class6/collateral/jinja2/intf_config3.j2: -------------------------------------------------------------------------------- 1 | 2 | interface FastEthernet4 3 | description *** LAN connection (don't change) *** 4 | ip address {{ ip_addr }} {{ netmask }} 5 | duplex auto 6 | speed auto 7 | ! 8 | {%- if vlan1 is defined %} 9 | interface Vlan1 10 | description *** Test description *** 11 | ip address {{ vlan1_ip_addr }} {{ vlan1_netmask }} 12 | ipv6 address {{ vlan1_ipv6_addr }}{{ vlan1_ipv6_netmask }} 13 | duplex auto 14 | speed auto 15 | {%- else %} 16 | not defined 17 | {%- endif %} 18 | 19 | -------------------------------------------------------------------------------- /class6/collateral/jinja2/intf_config_set.j2: -------------------------------------------------------------------------------- 1 | {% set new_netmask = '/24' %} 2 | interface FastEthernet4 3 | description *** LAN connection (don't change) *** 4 | ip address {{ ip_addr }}{{ new_netmask }} 5 | duplex auto 6 | speed auto 7 | -------------------------------------------------------------------------------- /class6/collateral/jinja2/nxos_bgp_include.j2: -------------------------------------------------------------------------------- 1 | 2 | feature bgp 3 | router bgp {{ local_as }} 4 | {% include 'bgp_ipv4_routes.j2' %} 5 | neighbor {{ peer1_ip }} remote-as {{ peer1_as }} 6 | update-source loopback1 7 | ebgp-multihop 2 8 | address-family ipv4 unicast 9 | -------------------------------------------------------------------------------- /class6/collateral/jinja2/nxos_bgp_include_2.j2: -------------------------------------------------------------------------------- 1 | 2 | feature bgp 3 | router bgp {{ local_as }} 4 | address-family ipv4 unicast 5 | network {{ advertised_route1 }} 6 | network {{ advertised_route2 }} 7 | network {{ advertised_route3 }} 8 | {%- include peer_template %} 9 | -------------------------------------------------------------------------------- /class6/collateral/jinja2/snmp_template.j2: -------------------------------------------------------------------------------- 1 | snmp-server group READONLY v3 priv read VIEWSTD access 98 2 | snmp-server view VIEWSTD iso included 3 | snmp-server community invalid RO 98 4 | snmp-server ifindex persist 5 | access-list 98 remark *** SNMP *** 6 | access-list 98 permit any 7 | -------------------------------------------------------------------------------- /class6/collateral/jinja2/switch_interfaces.j2: -------------------------------------------------------------------------------- 1 | {%- for port_number in range(1, 49) %} 2 | interface GigabitEthernet1/{{ port_number }} 3 | switchport mode access 4 | switchport access vlan {{ vlan }} 5 | spanning-tree portfast 6 | spanning-tree bpduguard enable 7 | ! 8 | {%- endfor %} 9 | -------------------------------------------------------------------------------- /class6/collateral/jinja2/switch_interfaces2.j2: -------------------------------------------------------------------------------- 1 | {%- for module in range(1, 3) %} 2 | {%- for port_number in range(1, 49) %} 3 | interface GigabitEthernet{{ module }}/{{ port_number }} 4 | switchport mode access 5 | switchport access vlan {{ vlan }} 6 | spanning-tree portfast 7 | spanning-tree bpduguard enable 8 | ! 9 | {%- endfor %} 10 | {%- endfor %} 11 | -------------------------------------------------------------------------------- /class6/collateral/jinja2/switch_interfaces3.j2: -------------------------------------------------------------------------------- 1 | {%- for module in modules %} 2 | {%- for port_number in range(1, 49) %} 3 | interface GigabitEthernet{{ module }}/{{ port_number }} 4 | switchport mode access 5 | switchport access vlan {{ vlan }} 6 | spanning-tree portfast 7 | spanning-tree bpduguard enable 8 | ! 9 | {%- endfor %} 10 | {%- endfor %} 11 | -------------------------------------------------------------------------------- /class6/collateral/jinja2/switch_interfaces4.j2: -------------------------------------------------------------------------------- 1 | {%- for module, module_vars in modules.items() %} 2 | {%- for port_number in module_vars['ports'] %} 3 | interface GigabitEthernet{{ module }}/{{ port_number }} 4 | switchport mode access 5 | switchport access vlan {{ module_vars.default_vlan }} 6 | spanning-tree portfast 7 | spanning-tree bpduguard enable 8 | ! 9 | {%- endfor %} 10 | {%- endfor %} 11 | -------------------------------------------------------------------------------- /class6/collateral/jinja2/switch_intf_macro.j2: -------------------------------------------------------------------------------- 1 | {%- macro intf_trunk(native_vlan=1, trunk_allowed_vlans=1) -%} 2 | switchport mode trunk 3 | switchport trunk native vlan {{ native_vlan }} 4 | switchport trunk allowed vlan {{ trunk_allowed_vlans }} 5 | {%- endmacro %} 6 | {%- macro intf_access(vlan=1) -%} 7 | switchport mode access 8 | switchport access vlan {{ vlan }} 9 | {%- endmacro -%} 10 | ! 11 | ! 12 | interface FastEthernet0 13 | {{ intf_trunk(native_vlan=1, trunk_allowed_vlans="1,100") }} 14 | ! 15 | interface FastEthernet1 16 | {{ intf_trunk(native_vlan=10, trunk_allowed_vlans="1,100") }} 17 | ! 18 | interface FastEthernet2 19 | {{ intf_access() }} 20 | ! 21 | interface FastEthernet3 22 | {{ intf_access(100) }} 23 | ! 24 | ! 25 | -------------------------------------------------------------------------------- /class6/collateral/jinja2/test_case10_lists_dicts.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from __future__ import unicode_literals, print_function 3 | from jinja2 import FileSystemLoader, StrictUndefined 4 | from jinja2.environment import Environment 5 | 6 | env = Environment(undefined=StrictUndefined) 7 | env.loader = FileSystemLoader('.') 8 | 9 | modules = { 10 | 1: {'default_vlan': 520, 'ports': range(1, 25)}, 11 | 2: {'default_vlan': 530, 'ports': range(1, 49)}, 12 | 3: {'default_vlan': 540, 'ports': range(1, 49)}, 13 | 4: {'default_vlan': 600, 'ports': range(1, 25)}, 14 | } 15 | 16 | intf_vars = { 17 | 'modules': modules, 18 | } 19 | 20 | template_file = 'switch_interfaces4.j2' 21 | template = env.get_template(template_file) 22 | output = template.render(**intf_vars) 23 | print(output) 24 | -------------------------------------------------------------------------------- /class6/collateral/jinja2/test_case11_include.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from __future__ import print_function, unicode_literals 3 | from jinja2 import FileSystemLoader, StrictUndefined 4 | from jinja2.environment import Environment 5 | 6 | env = Environment(undefined=StrictUndefined) 7 | env.loader = FileSystemLoader('.') 8 | 9 | bgp_vars = { 10 | "hostname": "test-rtr1", 11 | "local_as": 10, 12 | "peer1_ip": "10.255.255.2", 13 | "peer1_as": 20, 14 | "advertised_route1": "10.10.200.0/24", 15 | "advertised_route2": "10.10.201.0/24", 16 | "advertised_route3": "10.10.202.0/24", 17 | } 18 | 19 | template_file = 'nxos_bgp_include.j2' 20 | template = env.get_template(template_file) 21 | print(template.render(bgp_vars)) 22 | -------------------------------------------------------------------------------- /class6/collateral/jinja2/test_case12_include.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from __future__ import print_function, unicode_literals 3 | from jinja2 import FileSystemLoader, StrictUndefined 4 | from jinja2.environment import Environment 5 | 6 | env = Environment(undefined=StrictUndefined) 7 | env.loader = FileSystemLoader('.') 8 | 9 | bgp_vars = { 10 | "hostname": "test-rtr1", 11 | "peer_template": "bgp_peer_ipv6.j2", 12 | # "peer_template": "bgp_peer_ipv4.j2", 13 | "local_as": 10, 14 | "peer1_ip": "10.255.255.2", 15 | "peer1_as": 20, 16 | "advertised_route1": "10.10.200.0/24", 17 | "advertised_route2": "10.10.201.0/24", 18 | "advertised_route3": "10.10.202.0/24", 19 | } 20 | 21 | template_file = 'nxos_bgp_include_2.j2' 22 | template = env.get_template(template_file) 23 | print(template.render(bgp_vars)) 24 | -------------------------------------------------------------------------------- /class6/collateral/jinja2/test_case13_include.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from __future__ import print_function, unicode_literals 3 | from jinja2 import FileSystemLoader, StrictUndefined 4 | from jinja2.environment import Environment 5 | 6 | env = Environment(undefined=StrictUndefined) 7 | env.loader = FileSystemLoader('.') 8 | 9 | device_vars = {} 10 | 11 | template_file = 'cisco1_config_2.j2' 12 | template = env.get_template(template_file) 13 | print(template.render(device_vars)) 14 | -------------------------------------------------------------------------------- /class6/collateral/jinja2/test_case14_filters.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from __future__ import print_function, unicode_literals 3 | import jinja2 4 | 5 | my_vars = { 6 | "router1": "cisco 881, fremont, ca" 7 | } 8 | 9 | my_template = ''' 10 | {{ router1 }} 11 | {{ "%30s %30s" | format(router1, "hello") }} 12 | ''' 13 | 14 | ''' 15 | {{ router1 | upper }} 16 | {{ router1 | capitalize }} 17 | {{ router1 | center( 80) }} 18 | {{ router1 | upper | center( 80) }} 19 | {{ router2 | default('not defined') }} 20 | ''' 21 | 22 | template = jinja2.Template(my_template) 23 | print(template.render(my_vars)) 24 | -------------------------------------------------------------------------------- /class6/collateral/jinja2/test_case15_set.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from __future__ import unicode_literals, print_function 3 | import jinja2 4 | 5 | with open("intf_config_set.j2") as f: 6 | intf_config = f.read() 7 | 8 | intf_vars = { 9 | 'ip_addr': '10.220.88.20', 10 | 'netmask': '255.255.255.0', 11 | } 12 | 13 | template = jinja2.Template(intf_config) 14 | output = template.render(**intf_vars) 15 | print(output) 16 | -------------------------------------------------------------------------------- /class6/collateral/jinja2/test_case16_macros.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from __future__ import print_function, unicode_literals 3 | from jinja2 import FileSystemLoader, StrictUndefined 4 | from jinja2.environment import Environment 5 | 6 | env = Environment(undefined=StrictUndefined) 7 | env.loader = FileSystemLoader('.') 8 | 9 | device_vars = {} 10 | 11 | template_file = 'switch_intf_macro.j2' 12 | template = env.get_template(template_file) 13 | print(template.render(device_vars)) 14 | -------------------------------------------------------------------------------- /class6/collateral/jinja2/test_case1_basic.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from __future__ import unicode_literals, print_function 3 | import jinja2 4 | 5 | intf_config = """ 6 | interface FastEthernet4 7 | description *** LAN connection (don't change) *** 8 | ip address 10.220.88.20 255.255.255.0 9 | duplex auto 10 | speed auto 11 | 12 | """ 13 | 14 | template = jinja2.Template(intf_config) 15 | output = template.render() 16 | print(output) 17 | -------------------------------------------------------------------------------- /class6/collateral/jinja2/test_case2_basic.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from __future__ import unicode_literals, print_function 3 | import jinja2 4 | 5 | intf_config = """ 6 | interface FastEthernet4 7 | description *** LAN connection (don't change) *** 8 | ip address {{ ip_addr }} {{ netmask }} 9 | duplex auto 10 | speed auto 11 | 12 | """ 13 | 14 | intf_vars = { 15 | 'ip_addr': '10.220.88.20', 16 | 'netmask': '255.255.255.0', 17 | } 18 | 19 | template = jinja2.Template(intf_config) 20 | output = template.render(**intf_vars) 21 | print(output) 22 | -------------------------------------------------------------------------------- /class6/collateral/jinja2/test_case3_external_file.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from __future__ import unicode_literals, print_function 3 | import jinja2 4 | 5 | with open("intf_config.j2") as f: 6 | intf_config = f.read() 7 | 8 | intf_vars = { 9 | 'ip_addr': '10.220.88.20', 10 | 'netmask': '255.255.255.0', 11 | } 12 | 13 | template = jinja2.Template(intf_config) 14 | output = template.render(**intf_vars) 15 | print(output) 16 | -------------------------------------------------------------------------------- /class6/collateral/jinja2/test_case4_ext_file_formal.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from __future__ import unicode_literals, print_function 3 | from jinja2 import FileSystemLoader, StrictUndefined 4 | from jinja2.environment import Environment 5 | 6 | env = Environment(undefined=StrictUndefined) 7 | env.loader = FileSystemLoader('.') 8 | 9 | intf_vars = { 10 | 'ip_addr': '10.220.88.20', 11 | 'netmask': '255.255.255.0', 12 | } 13 | 14 | template_file = 'intf_config.j2' 15 | template = env.get_template(template_file) 16 | output = template.render(**intf_vars) 17 | print(output) 18 | -------------------------------------------------------------------------------- /class6/collateral/jinja2/test_case5_cond.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from __future__ import unicode_literals, print_function 3 | from jinja2 import FileSystemLoader, StrictUndefined 4 | from jinja2.environment import Environment 5 | 6 | env = Environment(undefined=StrictUndefined) 7 | env.loader = FileSystemLoader('.') 8 | 9 | intf_vars = { 10 | 'ip_addr': '10.220.88.20', 11 | 'netmask': '255.255.255.0', 12 | 'vlan1': True, 13 | 'vlan1_ip_addr': '10.220.89.1', 14 | 'vlan1_netmask': '255.255.255.0', 15 | } 16 | 17 | template_file = 'intf_config2.j2' 18 | template = env.get_template(template_file) 19 | output = template.render(**intf_vars) 20 | print(output) 21 | -------------------------------------------------------------------------------- /class6/collateral/jinja2/test_case6_cond.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from __future__ import unicode_literals, print_function 3 | from jinja2 import FileSystemLoader, StrictUndefined 4 | from jinja2.environment import Environment 5 | 6 | env = Environment(undefined=StrictUndefined) 7 | env.loader = FileSystemLoader('.') 8 | 9 | intf_vars = { 10 | 'ip_addr': '10.220.88.20', 11 | 'netmask': '255.255.255.0', 12 | # 'vlan1': True, 13 | 'vlan1_ip_addr': '10.220.89.1', 14 | 'vlan1_netmask': '255.255.255.0', 15 | 'vlan1_ipv6_addr': '2001:DB8:0:1::1', 16 | 'vlan1_ipv6_netmask': '/64', 17 | } 18 | 19 | template_file = 'intf_config3.j2' 20 | template = env.get_template(template_file) 21 | output = template.render(**intf_vars) 22 | print(output) 23 | -------------------------------------------------------------------------------- /class6/collateral/jinja2/test_case7_for_loop.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from __future__ import unicode_literals, print_function 3 | from jinja2 import FileSystemLoader, StrictUndefined 4 | from jinja2.environment import Environment 5 | 6 | env = Environment(undefined=StrictUndefined) 7 | env.loader = FileSystemLoader('.') 8 | 9 | intf_vars = { 10 | 'port_count': 48, 11 | 'vlan': 550, 12 | } 13 | 14 | template_file = 'switch_interfaces.j2' 15 | template = env.get_template(template_file) 16 | output = template.render(**intf_vars) 17 | print(output) 18 | -------------------------------------------------------------------------------- /class6/collateral/jinja2/test_case8_nested_for.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from __future__ import unicode_literals, print_function 3 | from jinja2 import FileSystemLoader, StrictUndefined 4 | from jinja2.environment import Environment 5 | 6 | env = Environment(undefined=StrictUndefined) 7 | env.loader = FileSystemLoader('.') 8 | 9 | intf_vars = { 10 | 'port_count': 48, 11 | 'vlan': 550, 12 | } 13 | 14 | template_file = 'switch_interfaces2.j2' 15 | template = env.get_template(template_file) 16 | output = template.render(**intf_vars) 17 | print(output) 18 | -------------------------------------------------------------------------------- /class6/collateral/jinja2/test_case9_lists.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from __future__ import unicode_literals, print_function 3 | from jinja2 import FileSystemLoader, StrictUndefined 4 | from jinja2.environment import Environment 5 | 6 | env = Environment(undefined=StrictUndefined) 7 | env.loader = FileSystemLoader('.') 8 | 9 | modules = [1, 2, 3, 4, 6, 7] 10 | intf_vars = { 11 | 'modules': modules, 12 | 'port_count': 48, 13 | 'vlan': 550, 14 | } 15 | 16 | template_file = 'switch_interfaces3.j2' 17 | template = env.get_template(template_file) 18 | output = template.render(**intf_vars) 19 | print(output) 20 | -------------------------------------------------------------------------------- /class6/collateral/nx-api/case1_json_basic.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function, unicode_literals 2 | from rpc_client import RPCClient 3 | from getpass import getpass 4 | from pprint import pprint 5 | 6 | import requests 7 | from requests.packages.urllib3.exceptions import InsecureRequestWarning 8 | requests.packages.urllib3.disable_warnings(InsecureRequestWarning) 9 | 10 | device = RPCClient(host='nxos1.twb-tech.com', 11 | username='pyclass', 12 | password=getpass(), 13 | transport='https', 14 | port=8443, 15 | verify=False) # Don't verify SSL cert 16 | 17 | # response = device.send_request(commands=['show version'], method='cli_ascii') 18 | response = device.send_request(commands=['show version'], method='cli') 19 | pprint(response) 20 | -------------------------------------------------------------------------------- /class6/collateral/nx-api/case2_xml_basic.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function, unicode_literals 2 | from xml_client import XMLClient 3 | from getpass import getpass 4 | 5 | import requests 6 | from requests.packages.urllib3.exceptions import InsecureRequestWarning 7 | requests.packages.urllib3.disable_warnings(InsecureRequestWarning) 8 | 9 | device = XMLClient(host='nxos1.twb-tech.com', 10 | username='pyclass', 11 | password=getpass(), 12 | transport='https', 13 | port=8443, 14 | verify=False) # Don't verify SSL cert 15 | 16 | response = device.send_request(commands=['show version'], method='cli_show') 17 | print() 18 | print('#' * 80) 19 | print(response) 20 | print('#' * 80) 21 | -------------------------------------------------------------------------------- /class6/collateral/nx-api/case3_pynxos_basic.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function, unicode_literals 2 | from pynxos.device import Device 3 | from getpass import getpass 4 | from pprint import pprint 5 | 6 | import requests 7 | from requests.packages.urllib3.exceptions import InsecureRequestWarning 8 | requests.packages.urllib3.disable_warnings(InsecureRequestWarning) 9 | 10 | device = Device(host='nxos2.twb-tech.com', 11 | username='pyclass', 12 | password=getpass(), 13 | transport='https', 14 | port=8443) 15 | 16 | print(device.show('show hostname')) 17 | 18 | # Show command 19 | command = 'show version' 20 | output = device.show(command) 21 | pprint(output) 22 | 23 | # Show comand with raw_text i.e. unstructured data 24 | command = 'show version' 25 | output = device.show(command, raw_text=True) 26 | print(output) 27 | 28 | # Config command 29 | commands = [ 30 | 'logging history size 300', 31 | ] 32 | device.config_list(commands) 33 | 34 | # Copy run to start 35 | results = device.save() 36 | 37 | # Method to create a checkpoint file 38 | 39 | # Method to rollback to checkpoint file 40 | 41 | # reboot method 42 | 43 | # set_boot_options method 44 | 45 | # Automatic facts 46 | pprint(device.facts) 47 | 48 | # FileCopy class and SCP capabilities 49 | -------------------------------------------------------------------------------- /class6/collateral/nx-api/rpc_client.py: -------------------------------------------------------------------------------- 1 | """ 2 | Based upon RPCClient object in pynxos libary. 3 | """ 4 | from __future__ import unicode_literals, print_function 5 | import requests 6 | from requests.auth import HTTPBasicAuth 7 | import json 8 | 9 | 10 | class RPCClient(object): 11 | def __init__(self, host, username, password, transport='https', port=None, verify=True): 12 | if transport not in ['http', 'https']: 13 | raise ValueError("'{}' is an invalid transport.".format(transport)) 14 | 15 | if port is None: 16 | if transport == 'http': 17 | port = 80 18 | elif transport == 'https': 19 | port = 443 20 | 21 | self.url = '%s://%s:%s/ins' % (transport, host, port) 22 | self.headers = {u'content-type': u'application/json-rpc'} 23 | self.username = username 24 | self.password = password 25 | self.verify = verify 26 | 27 | def _build_payload(self, commands, method, rpc_version='2.0', version=1): 28 | """Build the JSON-RPC payload. 29 | 30 | Example payload: 31 | [ 32 | { 33 | "jsonrpc": "2.0", 34 | "method": "cli", 35 | "params": { 36 | "cmd": "show ip arp", 37 | "version": 1.2 38 | }, 39 | "id": 1 40 | } 41 | ] 42 | 43 | """ 44 | payload_list = [] 45 | id_num = 1 46 | for command in commands: 47 | payload = dict(jsonrpc=rpc_version, 48 | method=method, 49 | params=dict(cmd=command, version=version), 50 | id=id_num,) 51 | payload_list.append(payload) 52 | id_num += 1 53 | print(payload_list) 54 | return payload_list 55 | 56 | def send_request(self, commands, method='cli', timeout=30): 57 | """ 58 | Send a HTTP/HTTPS request containing the JSON-RPC payload, headers, and username/password. 59 | 60 | method = cli for structured data response 61 | method = cli_ascii for a string response (still in JSON-RPC dict, but in 'msg' key) 62 | """ 63 | timeout = int(timeout) 64 | payload_list = self._build_payload(commands, method) 65 | response = requests.post(self.url, 66 | timeout=timeout, 67 | data=json.dumps(payload_list), 68 | headers=self.headers, 69 | auth=HTTPBasicAuth(self.username, self.password), 70 | verify=self.verify) 71 | response_list = json.loads(response.text) 72 | 73 | if isinstance(response_list, dict): 74 | response_list = [response_list] 75 | 76 | # Add the 'command' that was executed to the response dictionary 77 | for i, response_dict in enumerate(response_list): 78 | response_dict['command'] = commands[i] 79 | return response_list 80 | -------------------------------------------------------------------------------- /class6/collateral/nx-api/xml_client.py: -------------------------------------------------------------------------------- 1 | """ 2 | Based upon PR submitted to pynxos libary by Matt Schwen. 3 | """ 4 | from __future__ import print_function, unicode_literals 5 | import requests 6 | from requests.auth import HTTPBasicAuth 7 | 8 | 9 | class XMLClient(object): 10 | def __init__(self, host, username, password, transport='https', port=None, verify=True): 11 | if transport not in ['http', 'https']: 12 | raise ValueError("'%s' is an invalid transport." % transport) 13 | 14 | if port is None: 15 | if transport == 'http': 16 | port = 80 17 | elif transport == 'https': 18 | port = 443 19 | 20 | self.url = u'%s://%s:%s/ins' % (transport, host, port) 21 | self.headers = {u'content-type': u'application/xml'} 22 | self.username = username 23 | self.password = password 24 | self.verify = verify 25 | 26 | def _build_payload(self, commands, method, xml_version='1.0', version=1): 27 | if len(commands) > 1: 28 | command = 0 29 | # This section for multiple commands is probably not correct. 30 | for item in commands: 31 | if command == 0: 32 | command = item 33 | else: 34 | command = '{}{}{}'.format(command, ' ;', item) 35 | else: 36 | command = commands[0] 37 | 38 | payload = """ 39 | 40 | {version} 41 | {method} 42 | 0 43 | sid 44 | {command} 45 | xml 46 | """.format(xml_version=xml_version, version=version, 47 | method=method, command=command) 48 | 49 | print(payload) 50 | return payload 51 | 52 | def send_request(self, commands, method='cli_show', timeout=30): 53 | timeout = int(timeout) 54 | payload = self._build_payload(commands, method) 55 | response = requests.post(self.url, 56 | timeout=timeout, 57 | data=payload, 58 | headers=self.headers, 59 | auth=HTTPBasicAuth(self.username, self.password), 60 | verify=self.verify) 61 | response = response.text 62 | return response 63 | -------------------------------------------------------------------------------- /class6/exercises/arista_template.j2: -------------------------------------------------------------------------------- 1 | ! 2 | hostname sanfran-sw4 3 | ! 4 | ntp server 10.10.10.24 5 | ! 6 | {% include 'snmp.j2' %} 7 | ! 8 | spanning-tree mode mstp 9 | ! 10 | aaa authorization exec default local 11 | ! 12 | no aaa root 13 | ! 14 | {% include 'arista_users.j2' %} 15 | ! 16 | clock timezone America/Los_Angeles 17 | ! 18 | interface Ethernet1 19 | spanning-tree portfast 20 | spanning-tree cost 1 21 | ! 22 | interface Ethernet2 23 | ! 24 | interface Ethernet3 25 | ! 26 | interface Ethernet4 27 | ! 28 | interface Ethernet5 29 | ! 30 | interface Ethernet6 31 | ! 32 | interface Ethernet7 33 | ! 34 | interface Management1 35 | shutdown 36 | ! 37 | interface Vlan1 38 | ip address 10.10.88.31/24 39 | ! 40 | ip route 0.0.0.0/0 10.10.88.1 41 | ! 42 | ip routing 43 | ! 44 | management api http-commands 45 | no shutdown 46 | ! 47 | ! 48 | end 49 | -------------------------------------------------------------------------------- /class6/exercises/arista_users.j2: -------------------------------------------------------------------------------- 1 | username local privilege 15 secret 5 $1$C3VxRxcO$71x9abDD09yW.NIR8d2Lh0 2 | username admin privilege 15 secret 5 $1$C3VxRxcO$71x9abDD09yW.NIR8d2Lh0 3 | -------------------------------------------------------------------------------- /class6/exercises/ex1_jinja_ospf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Use Jinja2 to generate the following configuration: 4 | 5 | -------- 6 | router ospf 40 7 | network 10.220.88.0 0.0.0.255 area 0 8 | -------- 9 | 10 | The process ID, network, wildcard mask, and area should all be variables in the Jinja2 template. 11 | 12 | Use a template directly embedded in your Python script. 13 | 14 | """ 15 | from __future__ import print_function, unicode_literals 16 | import jinja2 17 | 18 | ospf_template = """ 19 | router ospf {{ process_id }} 20 | network {{ network }} {{ wildcard }} area {{ area }} 21 | 22 | """ 23 | 24 | ospf_vars = { 25 | 'process_id': 40, 26 | 'network': '10.220.88.0', 27 | 'wildcard': '0.0.0.255', 28 | 'area': 0, 29 | } 30 | 31 | template = jinja2.Template(ospf_template) 32 | output = template.render(**ospf_vars) 33 | print(output) 34 | -------------------------------------------------------------------------------- /class6/exercises/ex2_if_cond.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Expand upon exercise1 to generate the following: 4 | 5 | -------- 6 | 7 | interface Loopback0 8 | ip address 172.31.255.1 255.255.255.255 9 | 10 | router ospf 40 11 | network 10.220.88.0 0.0.0.255 area 0 12 | 13 | -------- 14 | 15 | The Jinja2 template should be read from an external file named 'ospf_config.j2'. 16 | 17 | The following items should all be variables in the template: process_id, network, wildcard, 18 | area, loopback0_addr, loopback0_mask. 19 | 20 | Additionally, the 'interface Loopback0' and its ip address configuration should only be generated 21 | if the loopback0_addr variable is defined (i.e. use an if-condition here). 22 | 23 | """ 24 | from __future__ import print_function, unicode_literals 25 | import jinja2 26 | 27 | filename = 'ospf_config.j2' 28 | with open(filename) as f: 29 | ospf_template = f.read() 30 | 31 | ospf_vars = { 32 | 'process_id': 40, 33 | 'network': '10.220.88.0', 34 | 'wildcard': '0.0.0.255', 35 | 'area': 0, 36 | 'loopback0_addr': '172.31.255.1', 37 | 'loopback0_mask': '255.255.255.255', 38 | } 39 | 40 | template = jinja2.Template(ospf_template) 41 | output = template.render(**ospf_vars) 42 | print(output) 43 | -------------------------------------------------------------------------------- /class6/exercises/ex3_for_loop.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Expand upon exercise2 to generate the following: 4 | 5 | -------- 6 | 7 | interface Loopback0 8 | ip address 172.31.255.1 255.255.255.255 9 | 10 | router ospf 40 11 | network 10.220.88.0 0.0.0.255 area 0 12 | network 172.31.255.28 0.0.0.0 area 1 13 | 14 | -------- 15 | 16 | The Jinja2 template should be read from an external file named 'ospf_config_for.j2'. 17 | 18 | The OSPF 'network' statements should be generated using a for-loop embedded in the Jinja2 19 | template. 20 | 21 | You should have a Python data structure named 'ospf_networks' that you iterate over (in your 22 | Jinja2). At the highest-level this data structure should be either a list or a dictionary. 23 | 24 | The following items should all be variables in the template: 25 | process_id 26 | network* 27 | wildcard* 28 | area* 29 | loopback0_addr 30 | loopback0_maks 31 | 32 | * Contained inside of the ospf_networks variable (inside the Jinja2 for-loop) 33 | 34 | Additionally, the interface Loopback0 and its ip address config should only be generated 35 | if the loopback0_addr variable is defined (i.e. use an if-condition here). 36 | 37 | """ 38 | from __future__ import print_function, unicode_literals 39 | import jinja2 40 | 41 | filename = 'ospf_config_for.j2' 42 | with open(filename) as f: 43 | ospf_template = f.read() 44 | 45 | ospf_networks = [ 46 | { 47 | 'network': '10.220.88.0', 48 | 'wildcard': '0.0.0.255', 49 | 'area': 0, 50 | }, 51 | { 52 | 'network': '172.31.255.28', 53 | 'wildcard': '0.0.0.0', 54 | 'area': 1, 55 | }, 56 | ] 57 | 58 | ospf_vars = { 59 | 'process_id': 40, 60 | 'ospf_networks': ospf_networks, 61 | 'loopback0_addr': '172.31.255.1', 62 | 'loopback0_mask': '255.255.255.255', 63 | } 64 | 65 | template = jinja2.Template(ospf_template) 66 | output = template.render(**ospf_vars) 67 | print(output) 68 | -------------------------------------------------------------------------------- /class6/exercises/ex4_arista_include.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Use Jinja2 templating to generate the following Arista configuration. 4 | 5 | -------- 6 | ! 7 | hostname sanfran-sw4 8 | ! 9 | ntp server 10.10.10.24 10 | ! 11 | snmp-server contact Isaac Newton 12 | snmp-server location San Francisco, CA 13 | snmp-server community foo ro SNMP 14 | ! 15 | spanning-tree mode mstp 16 | ! 17 | aaa authorization exec default local 18 | ! 19 | no aaa root 20 | ! 21 | username local privilege 15 secret 5 $1$C3VxRxcO$71x9abDD09yW.NIR8d2Lh0 22 | username admin privilege 15 secret 5 $1$C3VxRxcO$71x9abDD09yW.NIR8d2Lh0 23 | ! 24 | clock timezone America/Los_Angeles 25 | ! 26 | interface Ethernet1 27 | spanning-tree portfast 28 | spanning-tree cost 1 29 | ! 30 | interface Ethernet2 31 | ! 32 | interface Ethernet3 33 | ! 34 | interface Ethernet4 35 | ! 36 | interface Ethernet5 37 | ! 38 | interface Ethernet6 39 | ! 40 | interface Ethernet7 41 | ! 42 | interface Management1 43 | shutdown 44 | ! 45 | interface Vlan1 46 | ip address 10.10.88.31/24 47 | ! 48 | ip route 0.0.0.0/0 10.10.88.1 49 | ! 50 | ip routing 51 | ! 52 | management api http-commands 53 | no shutdown 54 | ! 55 | ! 56 | end 57 | -------- 58 | 59 | The main template should be stored in an external file named 'arista_template.j2'. 60 | 61 | This template should use the Jinja2 include statement to pull in two additional templates. The 62 | first template should be named 'arista_users.j2' and should contain the two username statements. 63 | The second template should be named 'snmp.j2' and should include all of the SNMP statments. 64 | 65 | The SNMP location, contact, and community should all be made into Jinja2 variables. The rest of the 66 | configuration can be hard-coded (i.e. you don't need any other variables besides those three SNMP 67 | variables). 68 | 69 | """ 70 | from __future__ import print_function, unicode_literals 71 | from jinja2 import FileSystemLoader, StrictUndefined 72 | from jinja2.environment import Environment 73 | 74 | env = Environment(undefined=StrictUndefined) 75 | env.loader = FileSystemLoader('.') 76 | 77 | device_vars = { 78 | 'snmp_location': 'San Francisco, CA', 79 | 'snmp_contact': 'Isaac Newton', 80 | 'snmp_community': 'foo', 81 | } 82 | 83 | template_file = 'arista_template.j2' 84 | template = env.get_template(template_file) 85 | print(template.render(device_vars)) 86 | -------------------------------------------------------------------------------- /class6/exercises/ex5_pynxos_show.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Use the pynxos library to create an NX-API connection to both nxos1.twb-tech.com and to 4 | nxos2.twb-tech.com. 5 | 6 | Use the pynxos 'show' method to retrieve 'show hostname' from each of the devices. 7 | Print this show hostname output to standard output. 8 | """ 9 | from __future__ import print_function, unicode_literals 10 | from pynxos.device import Device 11 | from getpass import getpass 12 | 13 | import requests 14 | from requests.packages.urllib3.exceptions import InsecureRequestWarning 15 | requests.packages.urllib3.disable_warnings(InsecureRequestWarning) 16 | 17 | password = getpass() 18 | nxos1 = { 19 | 'host': 'nxos1.twb-tech.com', 20 | 'username': 'pyclass', 21 | 'password': password, 22 | 'transport': 'https', 23 | 'port': 8443, 24 | } 25 | nxos2 = { 26 | 'host': 'nxos2.twb-tech.com', 27 | 'username': 'pyclass', 28 | 'password': password, 29 | 'transport': 'https', 30 | 'port': 8443, 31 | } 32 | 33 | print() 34 | for device in (nxos1, nxos2): 35 | nxapi_conn = Device(**device) 36 | print('-' * 40) 37 | print(nxapi_conn.show('show hostname')) 38 | print('-' * 40) 39 | print() 40 | -------------------------------------------------------------------------------- /class6/exercises/ex6_pynxos_routes.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Use the pynxos library and NX-API to retreive the output of 'show ip route vrf management' from the 4 | nxos1 switch. 5 | 6 | Parse the returned data structure and from this, retrieve the next hop for the 7 | default route. Print this to standard output. 8 | """ 9 | from __future__ import print_function, unicode_literals 10 | from pynxos.device import Device 11 | from getpass import getpass 12 | 13 | import requests 14 | from requests.packages.urllib3.exceptions import InsecureRequestWarning 15 | requests.packages.urllib3.disable_warnings(InsecureRequestWarning) 16 | 17 | 18 | def process_route_table(route_table): 19 | """Strip off unneeeded header information.""" 20 | route_table = route_table['TABLE_vrf']['ROW_vrf']['TABLE_addrf']['ROW_addrf'] 21 | return route_table['TABLE_prefix']['ROW_prefix'] 22 | 23 | 24 | def extract_next_hop(route_entry): 25 | route_data = route_entry['TABLE_path']['ROW_path'] 26 | return route_data['ipnexthop'] 27 | 28 | 29 | def main(): 30 | password = getpass() 31 | nxos1 = { 32 | 'host': 'nxos1.twb-tech.com', 33 | 'username': 'pyclass', 34 | 'password': password, 35 | 'transport': 'https', 36 | 'port': 8443, 37 | } 38 | nxos2 = { # noqa 39 | 'host': 'nxos2.twb-tech.com', 40 | 'username': 'pyclass', 41 | 'password': password, 42 | 'transport': 'https', 43 | 'port': 8443, 44 | } 45 | 46 | print() 47 | for device in (nxos1,): 48 | nxapi_conn = Device(**device) 49 | print('-' * 40) 50 | route_table = nxapi_conn.show('show ip route vrf management') 51 | route_table = process_route_table(route_table) 52 | for route_entry in route_table: 53 | if route_entry['ipprefix'] == '0.0.0.0/0': 54 | next_hop = extract_next_hop(route_entry) 55 | print("Default Gateway: {}".format(next_hop)) 56 | break 57 | print('-' * 40) 58 | print() 59 | 60 | 61 | if __name__ == "__main__": 62 | main() 63 | -------------------------------------------------------------------------------- /class6/exercises/ex7_pynxos_config.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Use the pynxos library to configure a loopback interface on nxos1. Choose a random 4 | loopback interface number between 1 and 99. 5 | 6 | Assign the loopback interface an IP address in the 172.16.0.0 - 172.31.255.255. Use 7 | a /32 netmask. 8 | 9 | Execute a 'show run interface loopbackX' command using NX-API to verify your interface 10 | was configured properly. For example: 11 | 12 | nxapi_conn.show('show run interface loopback99', raw_text=True) 13 | 14 | Note, you will need to use 'raw_text=True' for this command. 15 | """ 16 | from __future__ import print_function, unicode_literals 17 | from pynxos.device import Device 18 | from getpass import getpass 19 | 20 | import requests 21 | from requests.packages.urllib3.exceptions import InsecureRequestWarning 22 | requests.packages.urllib3.disable_warnings(InsecureRequestWarning) 23 | 24 | 25 | def main(): 26 | password = getpass() 27 | nxos1 = { 28 | 'host': 'nxos1.twb-tech.com', 29 | 'username': 'pyclass', 30 | 'password': password, 31 | 'transport': 'https', 32 | 'port': 8443, 33 | } 34 | nxos2 = { # noqa 35 | 'host': 'nxos2.twb-tech.com', 36 | 'username': 'pyclass', 37 | 'password': password, 38 | 'transport': 'https', 39 | 'port': 8443, 40 | } 41 | 42 | config_commands = ['interface Loopback99', 'ip address 172.31.254.99/32'] 43 | for device in (nxos1,): 44 | nxapi_conn = Device(**device) 45 | nxapi_conn.config_list(config_commands) 46 | output = nxapi_conn.show('show run interface loopback99', raw_text=True) 47 | print(output) 48 | 49 | 50 | if __name__ == "__main__": 51 | main() 52 | -------------------------------------------------------------------------------- /class6/exercises/nxos_global_ospf.j2: -------------------------------------------------------------------------------- 1 | feature ospf 2 | 3 | router ospf {{ process_id }} 4 | router-id {{ router_id }} 5 | -------------------------------------------------------------------------------- /class6/exercises/nxos_intf.j2: -------------------------------------------------------------------------------- 1 | interface Ethernet2/1 2 | ip address 172.16.88.1/24 3 | ip router ospf {{ process_id }} area {{ area }} 4 | 5 | interface loopback0 6 | ip address 172.31.255.1/24 7 | -------------------------------------------------------------------------------- /class6/exercises/ospf_config.j2: -------------------------------------------------------------------------------- 1 | {%- if loopback0_addr is defined %} 2 | interface Loopback0 3 | ip address {{ loopback0_addr }} {{ loopback0_mask }} 4 | {%- endif %} 5 | 6 | router ospf {{ process_id }} 7 | network {{ network }} {{ wildcard }} area {{ area }} 8 | 9 | -------------------------------------------------------------------------------- /class6/exercises/ospf_config_for.j2: -------------------------------------------------------------------------------- 1 | {%- if loopback0_addr is defined %} 2 | interface Loopback0 3 | ip address {{ loopback0_addr }} {{ loopback0_mask }} 4 | {%- endif %} 5 | 6 | router ospf {{ process_id }} 7 | {%- for net_entry in ospf_networks %} 8 | network {{ net_entry.network }} {{ net_entry.wildcard }} area {{ net_entry.area }} 9 | {%- endfor %} 10 | 11 | -------------------------------------------------------------------------------- /class6/exercises/snmp.j2: -------------------------------------------------------------------------------- 1 | snmp-server contact {{ snmp_contact }} 2 | snmp-server location {{ snmp_location }} 3 | snmp-server community {{ snmp_community }} ro SNMP 4 | -------------------------------------------------------------------------------- /class7/CFGS/eos/dns.j2: -------------------------------------------------------------------------------- 1 | ip name-server vrf default {{ dns1 }} 2 | ip name-server vrf default {{ dns2 }} 3 | 4 | -------------------------------------------------------------------------------- /class7/CFGS/eos/dns.txt: -------------------------------------------------------------------------------- 1 | ip name-server vrf default 1.1.1.1 2 | ip name-server vrf default 8.8.8.8 3 | -------------------------------------------------------------------------------- /class7/CFGS/eos/templates/dns.j2: -------------------------------------------------------------------------------- 1 | ip name-server vrf default {{ dns1 }} 2 | ip name-server vrf default {{ dns2 }} 3 | 4 | -------------------------------------------------------------------------------- /class7/CFGS/ios/dns.j2: -------------------------------------------------------------------------------- 1 | ip name-server {{ dns1 }} 2 | ip name-server {{ dns2 }} 3 | ip domain-lookup 4 | 5 | -------------------------------------------------------------------------------- /class7/CFGS/ios/dns.txt: -------------------------------------------------------------------------------- 1 | ip name-server 1.1.1.1 2 | ip name-server 8.8.8.8 3 | ip domain-lookup 4 | -------------------------------------------------------------------------------- /class7/CFGS/ios/templates/dns.j2: -------------------------------------------------------------------------------- 1 | ip name-server {{ dns1 }} 2 | ip name-server {{ dns2 }} 3 | ip domain-lookup 4 | 5 | -------------------------------------------------------------------------------- /class7/CFGS/nxos/dns.j2: -------------------------------------------------------------------------------- 1 | ip name-server {{ dns1 }} {{ dns2 }} use-vrf management 2 | 3 | -------------------------------------------------------------------------------- /class7/CFGS/nxos/dns.txt: -------------------------------------------------------------------------------- 1 | ip name-server 1.1.1.1 8.8.8.8 use-vrf management 2 | -------------------------------------------------------------------------------- /class7/CFGS/nxos/templates/dns.j2: -------------------------------------------------------------------------------- 1 | ip name-server {{ dns1 }} {{ dns2 }} use-vrf management 2 | 3 | -------------------------------------------------------------------------------- /class7/cisco_merge.txt: -------------------------------------------------------------------------------- 1 | ip route 1.1.99.1 255.255.255.255 10.220.88.1 2 | -------------------------------------------------------------------------------- /class7/exercise_notes.text: -------------------------------------------------------------------------------- 1 | Execute exercises using "python exercise name" (ex. python napalm_ex1.py) 2 | 3 | For napalm_ex6.py, directory had to be changed from ktbyers to schau 4 | # NAPALM load_template requires an absolute path 5 | - base_dir = '/home/kbyers/python_course/class7/CFGS/' 6 | + base_dir = '/home/schau/python_course/class7/CFGS/' 7 | 8 | For napalm_ex6_alt.py, NX-OS ping failing is expected (in the email notes already) 9 | -------------------------------------------------------------------------------- /class7/my_devices.py: -------------------------------------------------------------------------------- 1 | """ 2 | pynet-rtr1 (Cisco IOS) 184.105.247.70 3 | pynet-rtr2 (Cisco IOS) 184.105.247.71 4 | pynet-sw1 (Arista EOS) 184.105.247.72 5 | pynet-sw2 (Arista EOS) 184.105.247.73 6 | juniper-srx 184.105.247.76 7 | """ 8 | from getpass import getpass 9 | 10 | std_pwd = getpass("Enter standard password: ") 11 | 12 | pynet_rtr1 = { 13 | 'device_type': 'ios', 14 | 'hostname': '184.105.247.70', 15 | 'username': 'pyclass', 16 | 'password': std_pwd, 17 | 'optional_args': {}, 18 | } 19 | 20 | pynet_rtr2 = { 21 | 'device_type': 'ios', 22 | 'hostname': '184.105.247.71', 23 | 'username': 'pyclass', 24 | 'password': std_pwd, 25 | 'optional_args': {}, 26 | } 27 | 28 | pynet_sw1 = { 29 | 'device_type': 'eos', 30 | 'hostname': '184.105.247.72', 31 | 'username': 'pyclass', 32 | 'password': std_pwd, 33 | 'optional_args': {}, 34 | } 35 | 36 | pynet_sw2 = { 37 | 'device_type': 'eos', 38 | 'hostname': '184.105.247.73', 39 | 'username': 'pyclass', 40 | 'password': std_pwd, 41 | 'optional_args': {}, 42 | } 43 | 44 | juniper_srx = { 45 | 'device_type': 'junos', 46 | 'hostname': '184.105.247.76', 47 | 'username': 'pyclass', 48 | 'password': std_pwd, 49 | 'optional_args': {}, 50 | } 51 | 52 | nxos1 = { 53 | 'device_type': 'nxos', 54 | 'hostname': 'nxos1.twb-tech.com', 55 | 'username': 'pyclass', 56 | 'password': std_pwd, 57 | 'optional_args': {'port': 8443}, 58 | } 59 | 60 | device_list = [ 61 | pynet_rtr1, 62 | pynet_rtr2, 63 | pynet_sw1, 64 | pynet_sw2, 65 | juniper_srx, 66 | nxos1, 67 | ] 68 | -------------------------------------------------------------------------------- /class7/napalm_ex1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Connect to set of network devices using NAPALM (different platforms); print 4 | out the device facts. 5 | """ 6 | from __future__ import print_function, unicode_literals 7 | from napalm import get_network_driver 8 | from my_devices import device_list 9 | 10 | # Disable NX-OS certificate warning 11 | import requests 12 | from requests.packages.urllib3.exceptions import InsecureRequestWarning 13 | requests.packages.urllib3.disable_warnings(InsecureRequestWarning) 14 | 15 | 16 | def main(): 17 | """ 18 | Connect to set of network devices using NAPALM (different platforms); print 19 | out the facts. 20 | """ 21 | for a_device in device_list: 22 | device_type = a_device.pop('device_type') 23 | driver = get_network_driver(device_type) 24 | device = driver(**a_device) 25 | 26 | print() 27 | print(">>>Device open") 28 | device.open() 29 | 30 | print("-" * 50) 31 | device_facts = device.get_facts() 32 | print("{hostname}: Model={model}".format(**device_facts)) 33 | 34 | print() 35 | 36 | 37 | if __name__ == "__main__": 38 | main() 39 | -------------------------------------------------------------------------------- /class7/napalm_ex2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Using NAPALM retrieve 'get_lldp_neighbors' from pynet-rtr1 and from pynet-rtr2. Print out retrieved 4 | LLDP information to standard output. 5 | """ 6 | from __future__ import print_function, unicode_literals 7 | from pprint import pprint 8 | from napalm import get_network_driver 9 | from my_devices import pynet_rtr1, pynet_rtr2 10 | 11 | 12 | def main(): 13 | """Retrieve LLDP information.""" 14 | for a_device in (pynet_rtr1, pynet_rtr2): 15 | device_type = a_device.pop('device_type') 16 | driver = get_network_driver(device_type) 17 | device = driver(**a_device) 18 | 19 | print() 20 | print(">>>Device open") 21 | device.open() 22 | 23 | print("-" * 50) 24 | lldp_info = device.get_lldp_neighbors() 25 | hostname = a_device['hostname'] 26 | print("{hostname}:\n".format(hostname=hostname)) 27 | pprint(lldp_info) 28 | print() 29 | 30 | print() 31 | 32 | 33 | if __name__ == "__main__": 34 | main() 35 | -------------------------------------------------------------------------------- /class7/napalm_ex3.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Using NAPALM retreive 'get_bgp_neighbors' from pynet-rtr1. Parse the returned data structure to and 4 | verify that the BGP peer to 10.220.88.38 is in the established state ('is_up' field in the NAPALM 5 | returned data structure). 6 | """ 7 | from __future__ import print_function, unicode_literals 8 | from napalm import get_network_driver 9 | from my_devices import pynet_rtr1 10 | 11 | 12 | def retrive_bgp_neighbor(bgp_data, neighbor): 13 | """ 14 | Parse the output from NAPALM's get_bgp_neighbors() 15 | 16 | Retrieve the specified neighbor's BGP dictionary. 17 | """ 18 | return bgp_data['global']['peers'][neighbor] 19 | 20 | 21 | def main(): 22 | """Retrieve get_bgp_neighbors and parse the output.""" 23 | for a_device in (pynet_rtr1,): 24 | device_type = a_device.pop('device_type') 25 | driver = get_network_driver(device_type) 26 | device = driver(**a_device) 27 | 28 | print() 29 | print(">>>Device open") 30 | device.open() 31 | 32 | print("-" * 50) 33 | hostname = a_device['hostname'] 34 | print("{hostname}:\n".format(hostname=hostname)) 35 | 36 | # Retrieve BGP information and parse returned data 37 | bgp_info = device.get_bgp_neighbors() 38 | bgp_neighbor = '10.220.88.38' 39 | bgp_neighbor_dict = retrive_bgp_neighbor(bgp_info, bgp_neighbor) 40 | bgp_state = bgp_neighbor_dict['is_up'] 41 | print("BGP Neighbor: {}, BGP Established State: {}".format(bgp_neighbor, bgp_state)) 42 | print() 43 | 44 | print() 45 | 46 | 47 | if __name__ == "__main__": 48 | main() 49 | -------------------------------------------------------------------------------- /class7/napalm_ex4.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Using NAPALM retrieve get_interfaces from Arista switch2 (pynet_sw2). Find all of the interfaces 4 | that are in an UP-UP state (is_enabled=True, and is_up=True). Print all of these UP-UP interfaces 5 | to standard output. 6 | """ 7 | from __future__ import print_function, unicode_literals 8 | from napalm import get_network_driver 9 | from my_devices import pynet_sw2 10 | 11 | 12 | def check_up_up(intf_data): 13 | if intf_data['is_enabled'] and intf_data['is_up']: 14 | return True 15 | return False 16 | 17 | 18 | def main(): 19 | """Retrieve get_interfaces and find the up-up interfaces.""" 20 | for a_device in (pynet_sw2,): 21 | device_type = a_device.pop('device_type') 22 | driver = get_network_driver(device_type) 23 | device = driver(**a_device) 24 | 25 | print() 26 | print(">>>Device open") 27 | device.open() 28 | 29 | print("-" * 50) 30 | hostname = a_device['hostname'] 31 | print("{hostname}:\n".format(hostname=hostname)) 32 | 33 | intf_info = device.get_interfaces() 34 | 35 | print() 36 | print("UP-UP Interfaces: ") 37 | print("-" * 50) 38 | intf_list = [] 39 | for intf_name, intf_data in intf_info.items(): 40 | if check_up_up(intf_data): 41 | intf_list.append(intf_name) 42 | 43 | # Sort by nam 44 | intf_list.sort() 45 | for intf in intf_list: 46 | print(intf) 47 | 48 | print() 49 | 50 | 51 | if __name__ == "__main__": 52 | main() 53 | -------------------------------------------------------------------------------- /class7/napalm_ex5.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Test NAPALM config merge operations on one of the Cisco routers.""" 3 | from __future__ import print_function, unicode_literals 4 | from napalm import get_network_driver 5 | from my_devices import pynet_rtr1 6 | 7 | 8 | def main(): 9 | """Test NAPALM config merge operations on one of the Cisco routers.""" 10 | for a_device in (pynet_rtr1,): 11 | device_type = a_device.pop('device_type') 12 | driver = get_network_driver(device_type) 13 | device = driver(**a_device) 14 | 15 | print() 16 | print(">>>Device open") 17 | device.open() 18 | 19 | print() 20 | print(">>>Load config change (merge) - no commit") 21 | device.load_merge_candidate(filename='cisco_merge.txt') 22 | print(device.compare_config()) 23 | 24 | print() 25 | print(">>>Discard config change (merge)") 26 | device.discard_config() 27 | print(device.compare_config()) 28 | 29 | print() 30 | print(">>>Load config change (merge) - commit") 31 | device.load_merge_candidate(filename='cisco_merge.txt') 32 | print(device.compare_config()) 33 | device.commit_config() 34 | 35 | print() 36 | 37 | 38 | if __name__ == "__main__": 39 | main() 40 | -------------------------------------------------------------------------------- /class7/napalm_ex6.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Test NAPALM config merge operations on one of the Cisco routers.""" 3 | from __future__ import print_function, unicode_literals 4 | 5 | from napalm import get_network_driver 6 | from my_devices import pynet_rtr1, pynet_sw1, nxos1 7 | 8 | import requests 9 | from requests.packages.urllib3.exceptions import InsecureRequestWarning 10 | requests.packages.urllib3.disable_warnings(InsecureRequestWarning) 11 | 12 | 13 | def ping_google(device): 14 | "Use NAPALM to ping google.com to validate DNS resolution.""" 15 | print() 16 | print(">>>Test ping to google.com") 17 | try: 18 | ping_output = device.ping(destination='google.com') 19 | except NotImplementedError: 20 | print("Ping failed: ping() method not implemented") 21 | return 22 | if not ping_output == {}: 23 | probes_sent = int(ping_output['success']['probes_sent']) 24 | packet_loss = int(ping_output['success']['packet_loss']) 25 | successful_pings = probes_sent - packet_loss 26 | print("Probes sent: {}".format(probes_sent)) 27 | print("Packet loss: {}".format(packet_loss)) 28 | if successful_pings > 0: 29 | print("Pings Successful: {}".format(successful_pings)) 30 | return 31 | 32 | print("Ping failed") 33 | 34 | 35 | def main(): 36 | """Test NAPALM config merge operations on one of the Cisco routers.""" 37 | 38 | for a_device in (pynet_rtr1, pynet_sw1, nxos1): 39 | 40 | template_vars = { 41 | 'dns1': '1.1.1.1', 42 | 'dns2': '8.8.8.8', 43 | } 44 | 45 | device_type = a_device.pop('device_type') 46 | 47 | # NAPALM load_template requires an absolute path 48 | base_dir = '/home/kbyers/python_course/class7/CFGS/' 49 | 50 | print() 51 | print('-' * 50) 52 | print("Platform: {}".format(device_type)) 53 | print(">>>Device open") 54 | driver = get_network_driver(device_type) 55 | device = driver(**a_device) 56 | device.open() 57 | 58 | print() 59 | print(">>>Commit change") 60 | device.load_template("dns", template_path=base_dir, **template_vars) 61 | print(device.compare_config()) 62 | device.commit_config() 63 | 64 | ping_google(device) 65 | print("\n\n") 66 | 67 | print() 68 | 69 | 70 | if __name__ == "__main__": 71 | main() 72 | -------------------------------------------------------------------------------- /class7/napalm_ex6_alt.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Test NAPALM config merge operations on one of the Cisco routers.""" 3 | from __future__ import print_function, unicode_literals 4 | 5 | from jinja2 import FileSystemLoader, StrictUndefined 6 | from jinja2.environment import Environment 7 | 8 | from napalm import get_network_driver 9 | from my_devices import pynet_rtr1, pynet_sw1, nxos1 10 | 11 | import requests 12 | from requests.packages.urllib3.exceptions import InsecureRequestWarning 13 | requests.packages.urllib3.disable_warnings(InsecureRequestWarning) 14 | 15 | 16 | def generate_config(loader_dir): 17 | """Generate the device configuration from a template.""" 18 | jinja_env = Environment(undefined=StrictUndefined) 19 | template_vars = { 20 | 'dns1': '1.1.1.1', 21 | 'dns2': '8.8.8.8', 22 | } 23 | template_file = 'dns.j2' 24 | 25 | jinja_env.loader = FileSystemLoader(loader_dir) 26 | template = jinja_env.get_template(template_file) 27 | return template.render(template_vars) 28 | 29 | 30 | def ping_google(device): 31 | "Use NAPALM to ping google.com to validate DNS resolution.""" 32 | print() 33 | print(">>>Test ping to google.com") 34 | try: 35 | ping_output = device.ping(destination='google.com') 36 | except NotImplementedError: 37 | print("Ping failed: ping() method not implemented") 38 | return 39 | if not ping_output == {}: 40 | probes_sent = int(ping_output['success']['probes_sent']) 41 | packet_loss = int(ping_output['success']['packet_loss']) 42 | successful_pings = probes_sent - packet_loss 43 | print("Probes sent: {}".format(probes_sent)) 44 | print("Packet loss: {}".format(packet_loss)) 45 | if successful_pings > 0: 46 | print("Pings Successful: {}".format(successful_pings)) 47 | return 48 | 49 | print("Ping failed") 50 | 51 | 52 | def main(): 53 | """Test NAPALM config merge operations on one of the Cisco routers.""" 54 | 55 | for a_device in (pynet_rtr1, pynet_sw1, nxos1): 56 | device_type = a_device.pop('device_type') 57 | 58 | # Directory where template and config file will be stored 59 | base_dir = './CFGS/{}'.format(device_type) 60 | dns_file = '{}/dns.txt'.format(base_dir) 61 | 62 | # Generate config file from a template 63 | dns_output = generate_config(loader_dir=base_dir) 64 | 65 | # Write generated config to file 66 | with open(dns_file, 'w') as f: 67 | f.write(dns_output) 68 | 69 | print() 70 | print('-' * 50) 71 | print("Platform: {}".format(device_type)) 72 | print(">>>Device open") 73 | driver = get_network_driver(device_type) 74 | device = driver(**a_device) 75 | device.open() 76 | 77 | print() 78 | print(">>>Commit change") 79 | device.load_merge_candidate(filename=dns_file) 80 | print(device.compare_config()) 81 | device.commit_config() 82 | 83 | ping_google(device) 84 | print("\n\n") 85 | 86 | print() 87 | 88 | 89 | if __name__ == "__main__": 90 | main() 91 | -------------------------------------------------------------------------------- /class8/django_notes.txt: -------------------------------------------------------------------------------- 1 | # Some basic Django operations 2 | # Assumes Django >= 1.11 3 | 4 | 5 | # Initial DB configuration 6 | python manage.py migrate 7 | 8 | # Refreshing DB after changes to models.py (assumes app name = net_system) 9 | python manage.py makemigrations net_system 10 | python manage.py migrate 11 | 12 | 13 | # Using Django as an ORM will require the following 14 | export DJANGO_SETTINGS_MODULE=djproject.settings 15 | export PYTHONPATH=~/DJANGOX/djproject/ 16 | 17 | 18 | # Example object creation 19 | pynet_rtr1 = NetworkDevice( 20 | device_name='pynet-rtr1', 21 | device_type='cisco_ios', 22 | ip_address='1.1.1.1', 23 | port=22, 24 | ) 25 | pynet_rtr1.save() 26 | 27 | # Example object creation using get_or_create 28 | pynet_rtr2 = NetworkDevice.objects.get_or_create( 29 | device_name='pynet-rtr2', 30 | device_type='cisco_ios', 31 | ip_address='1.1.1.1', 32 | port=8022, 33 | ) 34 | 35 | 36 | # Examples retrieving objects 37 | $ python manage.py shell 38 | >>> from net_system.models import NetworkDevice 39 | >>> net_devices = NetworkDevice.objects.all() 40 | >>> print net_devices 41 | [, 42 | , 43 | , 44 | , 45 | , 46 | ] 47 | 48 | >>> a_device = NetworkDevice.objects.get(device_name='pynet-rtr1') 49 | >>> a_device 50 | 51 | 52 | # Example modifying an object 53 | >>> a_device.vendor = 'cisco' 54 | >>> a_device.vendor 55 | 'cisco' 56 | >>> a_device.save() 57 | 58 | # Example delete object 59 | >>> a_device.delete() 60 | 61 | 62 | # Example retrieving all related objects for a foreign-key relationship 63 | >>> cisco_creds.networkdevice_set.all() 64 | [, ] 65 | 66 | -------------------------------------------------------------------------------- /class8/ex1a_db_setup.txt: -------------------------------------------------------------------------------- 1 | 2 | $ python manage.py makemigrations net_system 3 | Migrations for 'net_system': 4 | 0001_initial.py: 5 | - Create model Credentials 6 | - Create model NetworkDevice 7 | 8 | $ python manage.py migrate 9 | Operations to perform: 10 | Synchronize unmigrated apps: messages, staticfiles 11 | Apply all migrations: net_system, admin, auth, contenttypes, sessions 12 | Synchronizing apps without migrations: 13 | Creating tables... 14 | Running deferred SQL... 15 | Installing custom SQL... 16 | Running migrations: 17 | Rendering model states... DONE 18 | Applying contenttypes.0001_initial... OK 19 | Applying auth.0001_initial... OK 20 | Applying admin.0001_initial... OK 21 | Applying contenttypes.0002_remove_content_type_name... OK 22 | Applying auth.0002_alter_permission_name_max_length... OK 23 | Applying auth.0003_alter_user_email_max_length... OK 24 | Applying auth.0004_alter_user_username_opts... OK 25 | Applying auth.0005_alter_user_last_login_null... OK 26 | Applying auth.0006_require_contenttypes_0002... OK 27 | Applying net_system.0001_initial... OK 28 | Applying sessions.0001_initial... OK 29 | 30 | [ djproject]$ cd net_system/ 31 | [ net_system]$ ls 32 | admin.py __init__.py load_credentials.py load_devices.py migrations models.py __pycache__ tests.py views.py 33 | 34 | [ net_system]$ python load_devices.py 35 | (, True) 36 | (, True) 37 | (, True) 38 | (, True) 39 | (, True) 40 | (, True) 41 | 42 | [ net_system]$ python load_credentials.py 43 | (, True) 44 | (, True) 45 | 46 | -------------------------------------------------------------------------------- /class8/ex1b_link_credentials.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Accomplish class #8, exercise1b using a script""" 3 | from __future__ import print_function, unicode_literals 4 | import django 5 | django.setup() 6 | from net_system.models import NetworkDevice, Credentials # noqa 7 | 8 | 9 | def main(): 10 | """Link credentials to devices""" 11 | net_devices = NetworkDevice.objects.all() 12 | creds = Credentials.objects.all() 13 | 14 | std_creds = creds[0] 15 | arista_creds = creds[1] 16 | 17 | for a_device in net_devices: 18 | if 'arista' in a_device.device_type: 19 | a_device.credentials = arista_creds 20 | else: 21 | a_device.credentials = std_creds 22 | a_device.save() 23 | 24 | for a_device in net_devices: 25 | print(a_device, a_device.credentials) 26 | 27 | 28 | if __name__ == "__main__": 29 | main() 30 | -------------------------------------------------------------------------------- /class8/ex1b_link_credentials.txt: -------------------------------------------------------------------------------- 1 | ''' 2 | Update the NetworkDevice objects such that each NetworkDevice links to the 3 | correct credentials. 4 | ''' 5 | 6 | # from ~/DJANGOX/djproject 7 | $ python manage.py shell 8 | Python 2.7.9 (default, Apr 1 2015, 18:18:03) 9 | [GCC 4.8.2 20140120 (Red Hat 4.8.2-16)] on linux2 10 | Type "help", "copyright", "credits" or "license" for more information. 11 | (InteractiveConsole) 12 | >>> 13 | >>> from net_system.models import NetworkDevice,Credentials 14 | >>> net_devices = NetworkDevice.objects.all() 15 | >>> creds = Credentials.objects.all() 16 | >>> 17 | >>> creds 18 | [, ] 19 | >>> std_creds = creds[0] 20 | >>> arista_creds = creds[1] 21 | >>> std_creds 22 | 23 | >>> arista_creds 24 | 25 | >>> 26 | >>> for a_device in net_devices: 27 | ... if 'arista' in a_device.device_type: 28 | ... a_device.credentials = arista_creds 29 | ... else: 30 | ... a_device.credentials = std_creds 31 | ... a_device.save() 32 | ... 33 | >>> 34 | >>> for a_device in net_devices: 35 | ... print(a_device, a_device.credentials) 36 | ... 37 | pynet-rtr1 pyclass 38 | pynet-sw1 admin1 39 | pynet-sw2 admin1 40 | pynet-sw3 admin1 41 | pynet-sw4 admin1 42 | juniper-srx pyclass 43 | pynet-rtr2 pyclass 44 | 45 | -------------------------------------------------------------------------------- /class8/ex2_vendor_field.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ''' 3 | Set the vendor field of each NetworkDevice to the appropriate vendor. Save 4 | this field to the database. 5 | ''' 6 | from __future__ import print_function, unicode_literals 7 | import django 8 | django.setup() 9 | from net_system.models import NetworkDevice # noqa 10 | 11 | 12 | def main(): 13 | ''' 14 | Set the vendor field for each NetworkDevice to the appropriate vendor. Save 15 | this field to the database. 16 | ''' 17 | devices = NetworkDevice.objects.all() 18 | for a_device in devices: 19 | if 'cisco' in a_device.device_type: 20 | a_device.vendor = 'Cisco' 21 | elif 'juniper' in a_device.device_type: 22 | a_device.vendor = 'Juniper' 23 | elif 'arista' in a_device.device_type: 24 | a_device.vendor = 'Arista' 25 | a_device.save() 26 | 27 | for a_device in devices: 28 | print(a_device, a_device.vendor) 29 | 30 | 31 | if __name__ == "__main__": 32 | main() 33 | -------------------------------------------------------------------------------- /class8/ex3_create_devices.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ''' 3 | Create two new test NetworkDevices in the database. Use both direct object creation 4 | and the .get_or_create() method to create the devices. 5 | ''' 6 | from __future__ import print_function, unicode_literals 7 | import django 8 | django.setup() 9 | from net_system.models import NetworkDevice # noqa 10 | 11 | 12 | def main(): 13 | ''' 14 | Create two new test NetworkDevices in the database. Use both direct object creation 15 | and the .get_or_create() method to create the devices. 16 | ''' 17 | try: 18 | ip_addr = raw_input("Enter IP address: ") 19 | except NameError: 20 | ip_addr = input("Enter IP address: ") 21 | 22 | test_rtr1 = NetworkDevice( 23 | device_name='test-rtr1', 24 | device_type='cisco_ios', 25 | ip_address=ip_addr, 26 | port=5022, 27 | ) 28 | test_rtr1.save() 29 | 30 | # Create object for rtr2 31 | NetworkDevice.objects.get_or_create( 32 | device_name='test-rtr2', 33 | device_type='cisco_ios', 34 | ip_address=ip_addr, 35 | port=5122, 36 | ) 37 | 38 | # Verify devices that currently exist 39 | print() 40 | devices = NetworkDevice.objects.all() 41 | for a_device in devices: 42 | print(a_device) 43 | print() 44 | 45 | 46 | if __name__ == "__main__": 47 | main() 48 | -------------------------------------------------------------------------------- /class8/ex4_delete_devices.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Remove the two objects created in exercise #3 from the database.""" 3 | from __future__ import print_function, unicode_literals 4 | import django 5 | django.setup() 6 | from net_system.models import NetworkDevice # noqa 7 | 8 | 9 | def main(): 10 | """Remove the two objects created in exercise #3 from the database.""" 11 | try: 12 | test_rtr1 = NetworkDevice.objects.get(device_name='test-rtr1') 13 | test_rtr2 = NetworkDevice.objects.get(device_name='test-rtr2') 14 | test_rtr1.delete() 15 | test_rtr2.delete() 16 | except NetworkDevice.DoesNotExist: 17 | pass 18 | 19 | # Verify devices that currently exist 20 | print() 21 | devices = NetworkDevice.objects.all() 22 | for a_device in devices: 23 | print(a_device) 24 | print() 25 | 26 | 27 | if __name__ == "__main__": 28 | main() 29 | -------------------------------------------------------------------------------- /class8/ex5_db_show_version.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Use Netmiko to connect to each of the devices in the database. Execute 4 | 'show version' on each device. Record the amount of time required to do this. 5 | """ 6 | from __future__ import print_function, unicode_literals 7 | from datetime import datetime 8 | from netmiko import ConnectHandler 9 | import django 10 | django.setup() 11 | from net_system.models import NetworkDevice # noqa 12 | 13 | 14 | def show_version(a_device): 15 | """Execute show version command using Netmiko.""" 16 | creds = a_device.credentials 17 | remote_conn = ConnectHandler(device_type=a_device.device_type, 18 | ip=a_device.ip_address, 19 | username=creds.username, 20 | password=creds.password, 21 | port=a_device.port, secret='') 22 | print() 23 | print('#' * 80) 24 | print(remote_conn.send_command_expect("show version")) 25 | print('#' * 80) 26 | print() 27 | 28 | 29 | def main(): 30 | """ 31 | Use Netmiko to connect to each of the devices in the database. Execute 32 | 'show version' on each device. 33 | """ 34 | start_time = datetime.now() 35 | devices = NetworkDevice.objects.all() 36 | for a_device in devices: 37 | show_version(a_device) 38 | 39 | elapsed_time = datetime.now() - start_time 40 | print("Elapsed time: {}".format(elapsed_time)) 41 | 42 | 43 | if __name__ == "__main__": 44 | main() 45 | -------------------------------------------------------------------------------- /class8/ex6_threads_show_ver.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ''' 3 | Use threads and Netmiko to connect to each of the devices in the database. Execute 4 | 'show version' on each device. Record the amount of time required to do this. 5 | ''' 6 | from __future__ import print_function, unicode_literals 7 | from netmiko import ConnectHandler 8 | from datetime import datetime 9 | import threading 10 | 11 | import django 12 | django.setup() 13 | from net_system.models import NetworkDevice # noqa 14 | 15 | 16 | def show_version(a_device): 17 | ''' 18 | Execute show version command using Netmiko 19 | ''' 20 | creds = a_device.credentials 21 | remote_conn = ConnectHandler(device_type=a_device.device_type, 22 | ip=a_device.ip_address, 23 | username=creds.username, 24 | password=creds.password, 25 | port=a_device.port, secret='') 26 | print() 27 | print('#' * 80) 28 | print(remote_conn.send_command_expect("show version")) 29 | print('#' * 80) 30 | print() 31 | remote_conn.disconnect() 32 | 33 | 34 | def main(): 35 | ''' 36 | Use threads and Netmiko to connect to each of the devices in the database. Execute 37 | 'show version' on each device. Record the amount of time required to do this. 38 | ''' 39 | start_time = datetime.now() 40 | devices = NetworkDevice.objects.all() 41 | 42 | for a_device in devices: 43 | my_thread = threading.Thread(target=show_version, args=(a_device,)) 44 | my_thread.start() 45 | 46 | main_thread = threading.currentThread() 47 | for some_thread in threading.enumerate(): 48 | if some_thread != main_thread: 49 | print(some_thread) 50 | some_thread.join() 51 | 52 | print("\nElapsed time: " + str(datetime.now() - start_time)) 53 | 54 | 55 | if __name__ == "__main__": 56 | main() 57 | -------------------------------------------------------------------------------- /class8/ex7_processes_show_ver.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ''' 3 | Use processes and Netmiko to connect to each of the devices in the database. Execute 4 | 'show version' on each device. Record the amount of time required to do this. 5 | ''' 6 | from __future__ import print_function, unicode_literals 7 | from netmiko import ConnectHandler 8 | from datetime import datetime 9 | from multiprocessing import Process 10 | import django 11 | django.setup() 12 | 13 | from net_system.models import NetworkDevice # noqa 14 | 15 | 16 | def show_version(a_device): 17 | ''' 18 | Execute show version command using Netmiko 19 | ''' 20 | creds = a_device.credentials 21 | remote_conn = ConnectHandler(device_type=a_device.device_type, 22 | ip=a_device.ip_address, 23 | username=creds.username, 24 | password=creds.password, 25 | port=a_device.port, secret='') 26 | print() 27 | print('#' * 80) 28 | print(remote_conn.send_command_expect("show version")) 29 | print('#' * 80) 30 | print() 31 | 32 | 33 | def main(): 34 | ''' 35 | Use processes and Netmiko to connect to each of the devices in the database. Execute 36 | 'show version' on each device. Record the amount of time required to do this. 37 | ''' 38 | start_time = datetime.now() 39 | devices = NetworkDevice.objects.all() 40 | 41 | procs = [] 42 | for a_device in devices: 43 | my_proc = Process(target=show_version, args=(a_device,)) 44 | my_proc.start() 45 | procs.append(my_proc) 46 | 47 | for a_proc in procs: 48 | print(a_proc) 49 | a_proc.join() 50 | 51 | print("\nElapsed time: " + str(datetime.now() - start_time)) 52 | 53 | 54 | if __name__ == "__main__": 55 | main() 56 | -------------------------------------------------------------------------------- /class8/ex8_proc_w_queue.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ''' 3 | Use processes and Netmiko to connect to each of the devices in the database. Execute 4 | 'show version' on each device. Use a queue to pass the output back to the parent process. 5 | Record the amount of time required to do this. 6 | ''' 7 | from __future__ import print_function, unicode_literals 8 | from netmiko import ConnectHandler 9 | from datetime import datetime 10 | from multiprocessing import Process, Queue 11 | 12 | import django 13 | django.setup() 14 | 15 | from net_system.models import NetworkDevice # noqa 16 | 17 | 18 | def show_version_queue(a_device, output_q): 19 | ''' 20 | Use Netmiko to execute show version. Use a queue to pass the data back to 21 | the main process. 22 | ''' 23 | output_dict = {} 24 | creds = a_device.credentials 25 | remote_conn = ConnectHandler(device_type=a_device.device_type, 26 | ip=a_device.ip_address, 27 | username=creds.username, password=creds.password, 28 | port=a_device.port, secret='', verbose=False) 29 | 30 | output = ('#' * 80) + "\n" 31 | output += remote_conn.send_command_expect("show version") + "\n" 32 | output += ('#' * 80) + "\n" 33 | output_dict[a_device.device_name] = output 34 | output_q.put(output_dict) 35 | 36 | 37 | def main(): 38 | ''' 39 | Use processes and Netmiko to connect to each of the devices in the database. Execute 40 | 'show version' on each device. Use a queue to pass the output back to the parent process. 41 | Record the amount of time required to do this. 42 | ''' 43 | start_time = datetime.now() 44 | output_q = Queue(maxsize=20) 45 | devices = NetworkDevice.objects.all() 46 | 47 | procs = [] 48 | for a_device in devices: 49 | my_proc = Process(target=show_version_queue, args=(a_device, output_q)) 50 | my_proc.start() 51 | procs.append(my_proc) 52 | 53 | # Make sure all processes have finished 54 | for a_proc in procs: 55 | a_proc.join() 56 | 57 | while not output_q.empty(): 58 | my_dict = output_q.get() 59 | for k, val in my_dict.items(): 60 | print(k) 61 | print(val) 62 | 63 | print("\nElapsed time: " + str(datetime.now() - start_time)) 64 | 65 | 66 | if __name__ == "__main__": 67 | main() 68 | -------------------------------------------------------------------------------- /class8/exercise_notes.txt: -------------------------------------------------------------------------------- 1 | 2 | # Initial Setup (just once to setup the DB) 3 | cd ~/DJANGOX/djproject 4 | python manage.py makemigrations net_system 5 | python manage.py migrate 6 | 7 | cd ~/DJANGOX/djproject/net_system 8 | python load_devices.py 9 | python load_credentials.py 10 | 11 | -------------------------------------------------------------------------------- /class9/exercise1/mytest/simple.py: -------------------------------------------------------------------------------- 1 | '''Python class on writing reusable code.''' 2 | from __future__ import print_function, unicode_literals 3 | 4 | 5 | def func2(): 6 | '''Simple test function''' 7 | print("Simple") 8 | 9 | 10 | if __name__ == "__main__": 11 | print("Main program - simple") 12 | -------------------------------------------------------------------------------- /class9/exercise1/mytest/whatever.py: -------------------------------------------------------------------------------- 1 | """Python class on writing reusable code.""" 2 | from __future__ import print_function, unicode_literals 3 | 4 | 5 | def func3(): 6 | '''Simple test function''' 7 | print("Whatever") 8 | 9 | 10 | if __name__ == "__main__": 11 | print("Main program - whatever") 12 | -------------------------------------------------------------------------------- /class9/exercise1/mytest/world.py: -------------------------------------------------------------------------------- 1 | """Python class on writing reusable code.""" 2 | from __future__ import print_function, unicode_literals 3 | 4 | 5 | def func1(): 6 | '''Simple test function''' 7 | print("Hello world") 8 | 9 | 10 | if __name__ == "__main__": 11 | print("Main program - world") 12 | -------------------------------------------------------------------------------- /class9/exercise1/test_loading.txt: -------------------------------------------------------------------------------- 1 | # Note this test was performed using PY2; recent versions of PY3 will allow you to import the 2 | # package even though no __init__.py exists (i.e. PY3 >= 3.3 will allow packages without 3 | # __init__.py files in certain contexts). 4 | 5 | $ python 6 | 7 | >>> import mytest 8 | Traceback (most recent call last): 9 | File "", line 1, in 10 | ImportError: No module named mytest 11 | 12 | >>> import mytest.world 13 | Traceback (most recent call last): 14 | File "", line 1, in 15 | ImportError: No module named mytest.world 16 | 17 | >>> import mytest.simple 18 | Traceback (most recent call last): 19 | File "", line 1, in 20 | ImportError: No module named mytest.simple 21 | 22 | -------------------------------------------------------------------------------- /class9/exercise2/mytest/__init__.py: -------------------------------------------------------------------------------- 1 | from mytest.world import func1 # noqa 2 | from mytest.simple import func2 # noqa 3 | from mytest.whatever import func3 # noqa 4 | -------------------------------------------------------------------------------- /class9/exercise2/mytest/simple.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Python class on writing reusable code 3 | ''' 4 | from __future__ import print_function, unicode_literals 5 | 6 | 7 | def func2(): 8 | '''Simple test function''' 9 | print("Simple") 10 | 11 | 12 | if __name__ == "__main__": 13 | print("Main program - simple") 14 | -------------------------------------------------------------------------------- /class9/exercise2/mytest/whatever.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Python class on writing reusable code 3 | ''' 4 | from __future__ import print_function, unicode_literals 5 | 6 | 7 | def func3(): 8 | '''Simple test function''' 9 | print("Whatever") 10 | 11 | 12 | if __name__ == "__main__": 13 | print("Main program - whatever") 14 | -------------------------------------------------------------------------------- /class9/exercise2/mytest/world.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Python class on writing reusable code 3 | ''' 4 | from __future__ import print_function, unicode_literals 5 | 6 | 7 | def func1(): 8 | '''Simple test function''' 9 | print("Hello world") 10 | 11 | 12 | if __name__ == "__main__": 13 | print("Main program - world") 14 | -------------------------------------------------------------------------------- /class9/exercise2/test_loading.txt: -------------------------------------------------------------------------------- 1 | $ python 2 | >>> import mytest 3 | >>> mytest.func1() 4 | Hello world 5 | >>> mytest.func2() 6 | Simple 7 | >>> mytest.func3() 8 | Whatever 9 | >>> quit() 10 | 11 | $ python 12 | >>> from mytest import func1, func2, func3 13 | >>> func1() 14 | Hello world 15 | >>> func2() 16 | Simple 17 | >>> func3() 18 | Whatever 19 | >>> 20 | 21 | -------------------------------------------------------------------------------- /class9/exercise3/mytest/__init__.py: -------------------------------------------------------------------------------- 1 | from mytest.world import func1 2 | from mytest.simple import func2 3 | from mytest.whatever import func3 4 | 5 | __all__ = ('func1', 'func2', 'func3') 6 | -------------------------------------------------------------------------------- /class9/exercise3/mytest/simple.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Python class on writing reusable code 3 | ''' 4 | from __future__ import print_function, unicode_literals 5 | 6 | 7 | def func2(): 8 | '''Simple test function''' 9 | print("Simple") 10 | 11 | 12 | if __name__ == "__main__": 13 | print("Main program - simple") 14 | -------------------------------------------------------------------------------- /class9/exercise3/mytest/whatever.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Python class on writing reusable code 3 | ''' 4 | from __future__ import print_function, unicode_literals 5 | 6 | 7 | def func3(): 8 | '''Simple test function''' 9 | print("Whatever") 10 | 11 | 12 | if __name__ == "__main__": 13 | print("Main program - whatever") 14 | -------------------------------------------------------------------------------- /class9/exercise3/mytest/world.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Python class on writing reusable code 3 | ''' 4 | from __future__ import print_function, unicode_literals 5 | 6 | 7 | def func1(): 8 | '''Simple test function''' 9 | print("Hello world") 10 | 11 | 12 | if __name__ == "__main__": 13 | print("Main program - world") 14 | -------------------------------------------------------------------------------- /class9/exercise3/test_loading.txt: -------------------------------------------------------------------------------- 1 | 2 | $ python 3 | >>> from mytest import * 4 | >>> func1() 5 | Hello world 6 | >>> func2() 7 | Simple 8 | >>> func3() 9 | Whatever 10 | >>> 11 | 12 | -------------------------------------------------------------------------------- /class9/exercise4/mytest/__init__.py: -------------------------------------------------------------------------------- 1 | from mytest.world import func1 2 | from mytest.simple import func2 3 | from mytest.whatever import func3 4 | 5 | __all__ = ('func1', 'func2', 'func3') 6 | -------------------------------------------------------------------------------- /class9/exercise4/mytest/simple.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Python class on writing reusable code 3 | ''' 4 | from __future__ import print_function, unicode_literals 5 | 6 | 7 | def func2(): 8 | '''Simple test function''' 9 | print("Simple") 10 | 11 | 12 | if __name__ == "__main__": 13 | print("Main program - simple") 14 | -------------------------------------------------------------------------------- /class9/exercise4/mytest/whatever.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Python class on writing reusable code 3 | ''' 4 | from __future__ import print_function, unicode_literals 5 | 6 | 7 | def func3(): 8 | '''Simple test function''' 9 | print("Whatever") 10 | 11 | 12 | if __name__ == "__main__": 13 | print("Main program - whatever") 14 | -------------------------------------------------------------------------------- /class9/exercise4/mytest/world.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Python class on writing reusable code 3 | ''' 4 | from __future__ import print_function, unicode_literals 5 | 6 | 7 | def func1(): 8 | '''Simple test function''' 9 | print("Hello world") 10 | 11 | 12 | class MyClass(object): 13 | '''Simple test class''' 14 | def __init__(self, var1, var2, var3): 15 | self.var1 = var1 16 | self.var2 = var2 17 | self.var3 = var3 18 | 19 | def hello(self): 20 | '''Simple test method''' 21 | print("Hello World: {} {} {}".format(self.var1, self.var2, self.var3)) 22 | 23 | def not_hello(self): 24 | '''Simple test method''' 25 | print("Goodbye: {} {} {}".format(self.var1, self.var2, self.var3)) 26 | 27 | 28 | if __name__ == "__main__": 29 | print("\nMain program - world") 30 | 31 | # Some test code 32 | my_obj = MyClass('SF', 'NYC', 'LA') 33 | print() 34 | print(my_obj.var1, my_obj.var2, my_obj.var3) 35 | my_obj.hello() 36 | my_obj.not_hello() 37 | print() 38 | -------------------------------------------------------------------------------- /class9/exercise5/mytest/__init__.py: -------------------------------------------------------------------------------- 1 | from mytest.world import func1 2 | from mytest.simple import func2 3 | from mytest.whatever import func3 4 | 5 | __all__ = ('func1', 'func2', 'func3') 6 | -------------------------------------------------------------------------------- /class9/exercise5/mytest/simple.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Python class on writing reusable code 3 | ''' 4 | from __future__ import print_function, unicode_literals 5 | 6 | 7 | def func2(): 8 | '''Simple test function''' 9 | print("Simple") 10 | 11 | 12 | if __name__ == "__main__": 13 | print("Main program - simple") 14 | -------------------------------------------------------------------------------- /class9/exercise5/mytest/whatever.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Python class on writing reusable code 3 | ''' 4 | from __future__ import print_function, unicode_literals 5 | 6 | 7 | def func3(): 8 | '''Simple test function''' 9 | print("Whatever") 10 | 11 | 12 | if __name__ == "__main__": 13 | print("Main program - whatever") 14 | -------------------------------------------------------------------------------- /class9/exercise5/mytest/world.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Python class on writing reusable code 3 | ''' 4 | from __future__ import print_function, unicode_literals 5 | 6 | 7 | def func1(): 8 | '''Simple test function''' 9 | print("Hello world") 10 | 11 | 12 | class MyClass(object): 13 | '''Simple test class''' 14 | def __init__(self, var1, var2, var3): 15 | self.var1 = var1 16 | self.var2 = var2 17 | self.var3 = var3 18 | 19 | def hello(self): 20 | '''Simple test method''' 21 | print("Hello World: {} {} {}".format(self.var1, self.var2, self.var3)) 22 | 23 | def not_hello(self): 24 | '''Simple test method''' 25 | print("Goodbye: {} {} {}".format(self.var1, self.var2, self.var3)) 26 | 27 | 28 | class MyChildClass(MyClass): 29 | '''Simple test class''' 30 | def hello(self): 31 | '''Simple test method''' 32 | print("Something else: {} {} {}".format(self.var1, self.var2, self.var3)) 33 | 34 | 35 | if __name__ == "__main__": 36 | print("\nMain program - world") 37 | 38 | print("\nTesting MyClass:") 39 | my_obj = MyClass('SF', 'NYC', 'LA') 40 | print(my_obj.var1, my_obj.var2, my_obj.var3) 41 | my_obj.hello() 42 | my_obj.not_hello() 43 | 44 | print("\nTesting MyChildClass:") 45 | new_obj = MyChildClass('X', 'Y', 'Z') 46 | new_obj.hello() 47 | new_obj.not_hello() 48 | print() 49 | -------------------------------------------------------------------------------- /class9/exercise6/mytest/__init__.py: -------------------------------------------------------------------------------- 1 | from mytest.world import func1 2 | from mytest.simple import func2 3 | from mytest.whatever import func3 4 | 5 | __all__ = ('func1', 'func2', 'func3') 6 | -------------------------------------------------------------------------------- /class9/exercise6/mytest/simple.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Python class on writing reusable code 3 | ''' 4 | from __future__ import print_function, unicode_literals 5 | 6 | 7 | def func2(): 8 | '''Simple test function''' 9 | print("Simple") 10 | 11 | 12 | if __name__ == "__main__": 13 | print("Main program - simple") 14 | -------------------------------------------------------------------------------- /class9/exercise6/mytest/whatever.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Python class on writing reusable code 3 | ''' 4 | from __future__ import print_function, unicode_literals 5 | 6 | 7 | def func3(): 8 | '''Simple test function''' 9 | print("Whatever") 10 | 11 | 12 | if __name__ == "__main__": 13 | print("Main program - whatever") 14 | -------------------------------------------------------------------------------- /class9/exercise6/mytest/world.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Python class on writing reusable code 3 | ''' 4 | from __future__ import print_function, unicode_literals 5 | 6 | 7 | def func1(): 8 | '''Simple test function''' 9 | print("Hello world") 10 | 11 | 12 | class MyClass(object): 13 | '''Simple test class''' 14 | def __init__(self, var1, var2, var3): 15 | self.var1 = var1 16 | self.var2 = var2 17 | self.var3 = var3 18 | 19 | def hello(self): 20 | '''Simple test method''' 21 | print("Hello World: {} {} {}".format(self.var1, self.var2, self.var3)) 22 | 23 | def not_hello(self): 24 | '''Simple test method''' 25 | print("Goodbye: {} {} {}".format(self.var1, self.var2, self.var3)) 26 | 27 | 28 | class MyChildClass(MyClass): 29 | ''' 30 | Test class augmenting __init__ 31 | 32 | Could use super() also 33 | ''' 34 | def __init__(self, var1, var2, var3): 35 | print("Do something more in __init__()") 36 | MyClass.__init__(self, var1, var2, var3) 37 | 38 | def hello(self): 39 | '''Simple test method''' 40 | print("Something else: {} {} {}".format(self.var1, self.var2, self.var3)) 41 | 42 | 43 | if __name__ == "__main__": 44 | 45 | print("\nMain program - world") 46 | 47 | print("\nTesting MyClass:") 48 | my_obj = MyClass('SF', 'NYC', 'LA') 49 | print(my_obj.var1, my_obj.var2, my_obj.var3) 50 | my_obj.hello() 51 | my_obj.not_hello() 52 | 53 | print("\nTesting MyChildClass:") 54 | new_obj = MyChildClass('X', 'Y', 'Z') 55 | new_obj.hello() 56 | new_obj.not_hello() 57 | print() 58 | -------------------------------------------------------------------------------- /class9/exercise7/exercise_notes.txt: -------------------------------------------------------------------------------- 1 | 2 | $ export PYTHONPATH=/home/kbyers/python_course/class9/exercise7 3 | $ env | grep PYT 4 | PYTHONPATH=/home/kbyers/python_course/class9/exercise7 5 | 6 | $ cd /tmp 7 | $ python 8 | 9 | >>> import sys 10 | >>> from pprint import pprint 11 | >>> pprint(sys.path) 12 | ['', 13 | '/home/kbyers/python_course/class9/exercise7', 14 | '/home/kbyers/VENV/py3_venv/local/lib64/python3.6/site-packages', 15 | '/home/kbyers/VENV/py3_venv/local/lib/python3.6/site-packages', 16 | '/home/kbyers/VENV/py3_venv/lib64/python3.6', 17 | '/home/kbyers/VENV/py3_venv/lib/python3.6', 18 | '/home/kbyers/VENV/py3_venv/lib64/python3.6/site-packages', 19 | '/home/kbyers/VENV/py3_venv/lib/python3.6/site-packages', 20 | '/home/kbyers/VENV/py3_venv/lib64/python3.6/lib-dynload', 21 | '/home/kbyers/VENV/py3_venv/local/lib/python3.6/dist-packages', 22 | '/usr/lib64/python3.6', 23 | '/usr/lib/python3.6'] 24 | >>> 25 | >>> import mytest 26 | >>> mytest.__file__ 27 | '/home/kbyers/python_course/class9/exercise7/mytest/__init__.py' 28 | -------------------------------------------------------------------------------- /class9/exercise7/mytest/__init__.py: -------------------------------------------------------------------------------- 1 | from mytest.world import func1 2 | from mytest.simple import func2 3 | from mytest.whatever import func3 4 | 5 | __all__ = ('func1', 'func2', 'func3') 6 | -------------------------------------------------------------------------------- /class9/exercise7/mytest/simple.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Python class on writing reusable code 3 | ''' 4 | from __future__ import print_function, unicode_literals 5 | 6 | 7 | def func2(): 8 | '''Simple test function''' 9 | print("Simple") 10 | 11 | 12 | if __name__ == "__main__": 13 | print("Main program - simple") 14 | -------------------------------------------------------------------------------- /class9/exercise7/mytest/whatever.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Python class on writing reusable code 3 | ''' 4 | from __future__ import print_function, unicode_literals 5 | 6 | 7 | def func3(): 8 | '''Simple test function''' 9 | print("Whatever") 10 | 11 | 12 | if __name__ == "__main__": 13 | print("Main program - whatever") 14 | -------------------------------------------------------------------------------- /class9/exercise7/mytest/world.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Python class on writing reusable code 3 | ''' 4 | from __future__ import print_function, unicode_literals 5 | 6 | 7 | def func1(): 8 | '''Simple test function''' 9 | print("Hello world") 10 | 11 | 12 | class MyClass(object): 13 | '''Simple test class''' 14 | def __init__(self, var1, var2, var3): 15 | self.var1 = var1 16 | self.var2 = var2 17 | self.var3 = var3 18 | 19 | def hello(self): 20 | '''Simple test method''' 21 | print("Hello World: {} {} {}".format(self.var1, self.var2, self.var3)) 22 | 23 | def not_hello(self): 24 | '''Simple test method''' 25 | print("Goodbye: {} {} {}".format(self.var1, self.var2, self.var3)) 26 | 27 | 28 | class MyChildClass(MyClass): 29 | '''Simple test class''' 30 | def hello(self): 31 | '''Simple test method''' 32 | print("Something else: {} {} {}".format(self.var1, self.var2, self.var3)) 33 | 34 | 35 | if __name__ == "__main__": 36 | print("\nMain program - world") 37 | 38 | print("\nTesting MyClass:") 39 | my_obj = MyClass('SF', 'NYC', 'LA') 40 | print(my_obj.var1, my_obj.var2, my_obj.var3) 41 | my_obj.hello() 42 | my_obj.not_hello() 43 | 44 | print("\nTesting MyChildClass:") 45 | new_obj = MyChildClass('X', 'Y', 'Z') 46 | new_obj.hello() 47 | new_obj.not_hello() 48 | print() 49 | -------------------------------------------------------------------------------- /class9/exercise8/exercise_notes.txt: -------------------------------------------------------------------------------- 1 | $ python 2 | 3 | >>> from mytest import * 4 | >>> dir() 5 | ['MyClass', '__builtins__', '__doc__', '__name__', '__package__', 'func1', 'func2', 'func3'] 6 | >>> func1() 7 | Hello world 8 | >>> func2() 9 | Simple 10 | >>> func3() 11 | Whatever 12 | >>> my_obj = MyClass('a', 'b', 'c') 13 | >>> my_obj.hello() 14 | Hello World: a b c 15 | 16 | -------------------------------------------------------------------------------- /class9/exercise8/mytest/__init__.py: -------------------------------------------------------------------------------- 1 | from mytest.world import func1, MyClass 2 | from mytest.simple import func2 3 | from mytest.whatever import func3 4 | 5 | __all__ = ('func1', 'func2', 'func3', 'MyClass') 6 | -------------------------------------------------------------------------------- /class9/exercise8/mytest/simple.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Python class on writing reusable code 3 | ''' 4 | from __future__ import print_function, unicode_literals 5 | 6 | 7 | def func2(): 8 | '''Simple test function''' 9 | print("Simple") 10 | 11 | 12 | if __name__ == "__main__": 13 | print("Main program - simple") 14 | -------------------------------------------------------------------------------- /class9/exercise8/mytest/whatever.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Python class on writing reusable code 3 | ''' 4 | from __future__ import print_function, unicode_literals 5 | 6 | 7 | def func3(): 8 | '''Simple test function''' 9 | print("Whatever") 10 | 11 | 12 | if __name__ == "__main__": 13 | print("Main program - whatever") 14 | -------------------------------------------------------------------------------- /class9/exercise8/mytest/world.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Python class on writing reusable code 3 | ''' 4 | from __future__ import print_function, unicode_literals 5 | 6 | 7 | def func1(): 8 | '''Simple test function''' 9 | print("Hello world") 10 | 11 | 12 | class MyClass(object): 13 | '''Simple test class''' 14 | def __init__(self, var1, var2, var3): 15 | self.var1 = var1 16 | self.var2 = var2 17 | self.var3 = var3 18 | 19 | def hello(self): 20 | '''Simple test method''' 21 | print("Hello World: {} {} {}".format(self.var1, self.var2, self.var3)) 22 | 23 | def not_hello(self): 24 | '''Simple test method''' 25 | print("Goodbye: {} {} {}".format(self.var1, self.var2, self.var3)) 26 | 27 | 28 | class MyChildClass(MyClass): 29 | '''Simple test class''' 30 | def hello(self): 31 | '''Simple test method''' 32 | print("Something else: {} {} {}".format(self.var1, self.var2, self.var3)) 33 | 34 | 35 | if __name__ == "__main__": 36 | print("\nMain program - world") 37 | 38 | print("\nTesting MyClass:") 39 | my_obj = MyClass('SF', 'NYC', 'LA') 40 | print(my_obj.var1, my_obj.var2, my_obj.var3) 41 | my_obj.hello() 42 | my_obj.not_hello() 43 | 44 | print("\nTesting MyChildClass:") 45 | new_obj = MyChildClass('X', 'Y', 'Z') 46 | new_obj.hello() 47 | new_obj.not_hello() 48 | print() 49 | -------------------------------------------------------------------------------- /class9/exercise9/ex9_test_script.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ''' 3 | Write a Python script in a different directory (not the one containing mytest). 4 | a. Verify that you can import mytest and call the three functions func1(), 5 | func2(), and func3(). 6 | b. Create an object that uses MyClass. Verify that you call the hello() and 7 | not_hello() methods. 8 | ''' 9 | from __future__ import print_function, unicode_literals 10 | from mytest import func1, func2, func3, MyClass 11 | 12 | 13 | def main(): 14 | '''Main func''' 15 | print() 16 | print("Testing func1...", end="") 17 | func1() 18 | print("Testing func2...", end="") 19 | func2() 20 | print("Testing func3...", end="") 21 | func3() 22 | 23 | print("\nTesting MyClass...") 24 | my_obj = MyClass('a', 'b', 'c') 25 | my_obj.hello() 26 | my_obj.not_hello() 27 | print() 28 | 29 | 30 | if __name__ == "__main__": 31 | main() 32 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [pylama] 2 | linters = mccabe,pep8,pyflakes 3 | ignore = D203,C901 4 | skip = tests/*,examples/*,build/*,.tox/* 5 | 6 | [pylama:pep8] 7 | max_line_length = 100 8 | -------------------------------------------------------------------------------- /xml_juniper_bonus/collateral/case1_xml_parse.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals, print_function 2 | from lxml import etree 3 | 4 | with open('show_version.xml') as f: 5 | my_tree = etree.fromstring(f.read()) 6 | 7 | print(my_tree) 8 | -------------------------------------------------------------------------------- /xml_juniper_bonus/collateral/case2_print_xml.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals, print_function 2 | from lxml import etree 3 | 4 | with open('show_version.xml') as f: 5 | show_version = etree.fromstring(f.read()) 6 | 7 | print(show_version) 8 | print(etree.tostring(show_version, pretty_print=True).decode()) 9 | -------------------------------------------------------------------------------- /xml_juniper_bonus/collateral/case3_traverse_xml.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals, print_function 2 | from lxml import etree 3 | 4 | with open('show_version.xml') as f: 5 | show_version = etree.fromstring(f.read()) 6 | 7 | print() 8 | print("Our XML") 9 | print("-" * 20) 10 | print(etree.tostring(show_version, pretty_print=True).decode().rstrip()) 11 | print("-" * 20) 12 | print() 13 | 14 | print("Print the tag name of the root element:") 15 | print("-" * 20) 16 | print(show_version.tag) 17 | print() 18 | 19 | print("Access the child elements as list indices:") 20 | print("-" * 20) 21 | print(show_version[0].tag) 22 | print(show_version[1].tag) 23 | print(show_version[2].tag) 24 | print(show_version[3].tag) 25 | print(show_version[4].tag) 26 | print(show_version[5].tag) 27 | print() 28 | 29 | print("Looping over the child elements:") 30 | print("-" * 20) 31 | for child in show_version: 32 | print(child.tag) 33 | print() 34 | 35 | print("You can also access attributes: ") 36 | print("-" * 20) 37 | host1 = show_version[0] 38 | print("Model: {}".format(host1.get('model'))) 39 | print("Showing host1 .keys(): {}".format(host1.keys())) 40 | print("Showing host1 .items(): {}".format(host1.items())) 41 | print() 42 | 43 | print("You can also access text nodes: ") 44 | print("-" * 20) 45 | print(host1.text) 46 | print() 47 | -------------------------------------------------------------------------------- /xml_juniper_bonus/collateral/case4_xmltodict.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals, print_function 2 | from lxml import etree 3 | import xmltodict 4 | 5 | with open('show_version.xml') as f: 6 | show_version = etree.fromstring(f.read()) 7 | 8 | print() 9 | print("Our XML") 10 | print("-" * 20) 11 | print(etree.tostring(show_version, pretty_print=True).decode().rstrip()) 12 | print("-" * 20) 13 | print() 14 | 15 | show_version_dict = xmltodict.parse(etree.tostring(show_version, encoding='unicode')) 16 | -------------------------------------------------------------------------------- /xml_juniper_bonus/collateral/juniper_rpc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from __future__ import print_function, unicode_literals 3 | 4 | from jnpr.junos import Device 5 | from lxml import etree 6 | from getpass import getpass 7 | 8 | juniper_srx = { 9 | "host": "srx1.twb-tech.com", 10 | "user": "pyclass", 11 | "password": getpass(), 12 | } 13 | 14 | a_device = Device(**juniper_srx) 15 | a_device.open() 16 | 17 | # show version | display xml rpc 18 | # get-software-information 19 | # show_version = a_device.rpc.get_software_information() 20 | 21 | # get-lldp-neighbors-information 22 | lldp = a_device.rpc.get_lldp_neighbors_information() 23 | print(etree.tostring(lldp, encoding='unicode', pretty_print=True)) 24 | -------------------------------------------------------------------------------- /xml_juniper_bonus/collateral/show_version.xml: -------------------------------------------------------------------------------- 1 | 2 | pynet-jnpr-srx1 3 | pynet-jnpr-srx2 4 | srx100h2 5 | srx100h2 6 | 7 | 8 | junos 9 | JUNOS Software Release [12.1X44-D35.5] 10 | 11 | 12 | -------------------------------------------------------------------------------- /xml_juniper_bonus/exercises/ex1_xml_lldp.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Read in the file named 'show_lldp.xml'. This file is from 4 | 'show lldp neighbors | display xml' on a Juniper SRX (modified somewhat). 5 | 6 | Use etree.tostring() to print out the XML tree as a string. 7 | 8 | Use getchildren() or list indices to find the one child element of the root element. Print out 9 | this element's tag name. 10 | 11 | Using either a for-loop or getchildren(), find the text values associated with the following 12 | tags: 'lldp-local-interface', 'lldp-remote-system-name', 'lldp-remote-port-description'. Save 13 | these three items to a data structure with the following format: 14 | 15 | { local_intf: 16 | { 17 | 'remote_port': value, 18 | 'remote_sys_name': value, 19 | } 20 | } 21 | 22 | Print this data structure out to the screen. Your printed data structure should match the 23 | following: 24 | 25 | {'fe-0/0/7.0': {'remote_port': '24', 'remote_sys_name': 'twb-sf-hpsw1'}} 26 | 27 | """ 28 | from __future__ import unicode_literals, print_function 29 | from lxml import etree 30 | from pprint import pprint 31 | 32 | with open('show_lldp.xml') as f: 33 | lldp = etree.fromstring(f.read()) 34 | 35 | print() 36 | print("Print XML Tree out as a string:") 37 | print("-" * 20) 38 | print(etree.tostring(lldp, pretty_print=True).decode()) 39 | 40 | print('\n\n') 41 | print("Print one child element of the root element:") 42 | print("-" * 20) 43 | lldp_child = lldp.getchildren()[0] 44 | print(lldp_child.tag) 45 | 46 | for child in lldp_child: 47 | if child.tag == 'lldp-local-interface': 48 | local_intf = child.text 49 | elif child.tag == 'lldp-remote-system-name': 50 | remote_sys_name = child.text 51 | elif child.tag == 'lldp-remote-port-description': 52 | remote_port = child.text 53 | 54 | lldp_dict = { 55 | local_intf: { 56 | 'remote_sys_name': remote_sys_name, 57 | 'remote_port': remote_port, 58 | } 59 | } 60 | 61 | print('\n\n') 62 | print("Parse the returned data and create a dictionary.") 63 | print("-" * 20) 64 | pprint(lldp_dict) 65 | print('\n\n') 66 | -------------------------------------------------------------------------------- /xml_juniper_bonus/exercises/ex2_xml_arp.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Read in the file named 'show_arp.xml'. This file is from 'show arp | display xml' on a 4 | Juniper SRX (modified somewhat). 5 | 6 | Use etree.tostring() to print out the XML tree as a string. 7 | 8 | Use XPath parsing to find all of the arp entries and to construct the following 9 | dictionary: 10 | 11 | { 12 | '10.220.88.1': 13 | { 14 | 'intf': 'vlan.0', 15 | 'mac_addr': '00:62:ec:29:70:fe' 16 | }, 17 | '10.220.88.20': 18 | { 19 | 'intf': 'vlan.0', 20 | 'mac_addr': 'c8:9c:1d:ea:0e:b6' 21 | } 22 | } 23 | 24 | Print this dictionary out to standard output. 25 | """ 26 | from __future__ import unicode_literals, print_function 27 | from lxml import etree 28 | from pprint import pprint 29 | 30 | with open('show_arp.xml') as f: 31 | arp = etree.fromstring(f.read()) 32 | 33 | print() 34 | print("Print XML Tree out as a string:") 35 | print("-" * 20) 36 | print(etree.tostring(arp, pretty_print=True).decode()) 37 | 38 | xpath_arp = '//arp-table-entry' 39 | arp_entries = arp.xpath(xpath_arp) 40 | 41 | mac_xpath = 'mac-address' 42 | ip_xpath = 'ip-address' 43 | intf_xpath = 'interface-name' 44 | 45 | arp_dict = {} 46 | for arp_entry in arp_entries: 47 | mac_address = arp_entry.xpath(mac_xpath)[0].text 48 | ip_address = arp_entry.xpath(ip_xpath)[0].text 49 | intf = arp_entry.xpath(intf_xpath)[0].text 50 | arp_dict[ip_address] = { 51 | 'mac_addr': mac_address, 52 | 'intf': intf 53 | } 54 | 55 | print() 56 | print("Print out final data structure: ") 57 | print("-" * 20) 58 | pprint(arp_dict) 59 | print() 60 | -------------------------------------------------------------------------------- /xml_juniper_bonus/exercises/ex3_pyez_facts.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Connect to Juniper device using PyEZ. Display device facts.""" 3 | from __future__ import print_function, unicode_literals 4 | 5 | from jnpr.junos import Device 6 | from getpass import getpass 7 | from pprint import pprint 8 | 9 | 10 | def main(): 11 | """Connect to Juniper device using PyEZ. Display device facts.""" 12 | pwd = getpass() 13 | try: 14 | ip_addr = raw_input("Enter Juniper SRX IP: ") 15 | except NameError: 16 | ip_addr = input("Enter Juniper SRX IP: ") 17 | ip_addr = ip_addr.strip() 18 | 19 | juniper_srx = { 20 | "host": ip_addr, 21 | "user": "pyclass", 22 | "password": pwd 23 | } 24 | 25 | print("\n\nConnecting to Juniper SRX...\n") 26 | a_device = Device(**juniper_srx) 27 | a_device.open() 28 | pprint(a_device.facts) 29 | print() 30 | 31 | 32 | if __name__ == "__main__": 33 | main() 34 | -------------------------------------------------------------------------------- /xml_juniper_bonus/exercises/ex4_pyez_eth.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ''' 3 | Connect to Juniper device using PyEZ. Display operational state and pkts_in, pkts_out for all 4 | of the interfaces. 5 | ''' 6 | from __future__ import print_function, unicode_literals 7 | 8 | from jnpr.junos import Device 9 | from jnpr.junos.op.ethport import EthPortTable 10 | from getpass import getpass 11 | 12 | 13 | def main(): 14 | ''' 15 | Connect to Juniper device using PyEZ. Display operational state and pkts_in, pkts_out for all 16 | of the interfaces. 17 | ''' 18 | pwd = getpass() 19 | try: 20 | ip_addr = raw_input("Enter Juniper SRX IP: ") 21 | except NameError: 22 | ip_addr = input("Enter Juniper SRX IP: ") 23 | ip_addr = ip_addr.strip() 24 | 25 | juniper_srx = { 26 | "host": ip_addr, 27 | "user": "pyclass", 28 | "password": pwd 29 | } 30 | 31 | print("\n\nConnecting to Juniper SRX...\n") 32 | a_device = Device(**juniper_srx) 33 | a_device.open() 34 | 35 | eth_ports = EthPortTable(a_device) 36 | eth_ports.get() 37 | 38 | print("{:>15} {:>12} {:>12} {:>12}".format("INTF", "OPER STATE", "IN PACKETS", "OUT PACKETS")) 39 | for intf, eth_stats in eth_ports.items(): 40 | eth_stats = dict(eth_stats) 41 | oper_state = eth_stats['oper'] 42 | pkts_in = eth_stats['rx_packets'] 43 | pkts_out = eth_stats['tx_packets'] 44 | print("{:>15} {:>12} {:>12} {:>12}".format(intf, oper_state, pkts_in, pkts_out)) 45 | print() 46 | 47 | 48 | if __name__ == "__main__": 49 | main() 50 | -------------------------------------------------------------------------------- /xml_juniper_bonus/exercises/ex5_pyez_routes.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ''' 3 | Connect to Juniper device using PyEZ. Display the routing table. 4 | ''' 5 | from __future__ import print_function, unicode_literals 6 | 7 | from jnpr.junos import Device 8 | from jnpr.junos.op.routes import RouteTable 9 | from getpass import getpass 10 | 11 | 12 | def main(): 13 | ''' 14 | Connect to Juniper device using PyEZ. Display the routing table. 15 | ''' 16 | pwd = getpass() 17 | try: 18 | ip_addr = raw_input("Enter Juniper SRX IP: ") 19 | except NameError: 20 | ip_addr = input("Enter Juniper SRX IP: ") 21 | ip_addr = ip_addr.strip() 22 | 23 | juniper_srx = { 24 | "host": ip_addr, 25 | "user": "pyclass", 26 | "password": pwd 27 | } 28 | 29 | print("\n\nConnecting to Juniper SRX...\n") 30 | a_device = Device(**juniper_srx) 31 | a_device.open() 32 | 33 | routes = RouteTable(a_device) 34 | routes.get() 35 | 36 | print("\nJuniper SRX Routing Table: ") 37 | for a_route, route_attr in routes.items(): 38 | print("\n" + a_route) 39 | for attr_desc, attr_value in route_attr: 40 | print(" {} {}".format(attr_desc, attr_value)) 41 | 42 | print("\n") 43 | 44 | 45 | if __name__ == "__main__": 46 | main() 47 | -------------------------------------------------------------------------------- /xml_juniper_bonus/exercises/ex6_pyez_change_hostname.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ''' 3 | Exercise using Juniper's PyEZ to make changes to device in various ways 4 | 5 | Using the PyEZ load method set the hostname on the device using set, conf, and XML formats. 6 | 7 | Display the differences between the running config and candidate config after each change. 8 | Perform at least one commit and one rollback(0) during this process. 9 | 10 | The hostname at the end of the testing should be: pynet-jnpr-srx1 11 | ''' 12 | from __future__ import print_function, unicode_literals 13 | 14 | from jnpr.junos import Device 15 | from jnpr.junos.utils.config import Config 16 | from getpass import getpass 17 | 18 | 19 | def main(): 20 | ''' 21 | Exercise using Juniper's PyEZ to make changes to device in various ways 22 | ''' 23 | pwd = getpass() 24 | try: 25 | ip_addr = raw_input("Enter Juniper SRX IP: ") 26 | except NameError: 27 | ip_addr = input("Enter Juniper SRX IP: ") 28 | ip_addr = ip_addr.strip() 29 | 30 | juniper_srx = { 31 | "host": ip_addr, 32 | "user": "pyclass", 33 | "password": pwd 34 | } 35 | 36 | print("\n\nConnecting to Juniper SRX...\n") 37 | a_device = Device(**juniper_srx) 38 | a_device.open() 39 | 40 | cfg = Config(a_device) 41 | 42 | print("Setting hostname using set notation") 43 | cfg.load("set system host-name test1", format="set", merge=True) 44 | 45 | print("Current config differences: ") 46 | print(cfg.diff()) 47 | 48 | print("Performing rollback") 49 | cfg.rollback(0) 50 | 51 | print("\nSetting hostname using {} notation (external file)") 52 | cfg.load(path="load_hostname.conf", format="text", merge=True) 53 | 54 | print("Current config differences: ") 55 | print(cfg.diff()) 56 | 57 | print("Performing commit") 58 | cfg.commit() 59 | 60 | print("\nSetting hostname using XML (external file)") 61 | cfg.load(path="load_hostname.xml", format="xml", merge=True) 62 | 63 | print("Current config differences: ") 64 | print(cfg.diff()) 65 | 66 | print("Performing commit") 67 | cfg.commit() 68 | print() 69 | 70 | 71 | if __name__ == "__main__": 72 | main() 73 | -------------------------------------------------------------------------------- /xml_juniper_bonus/exercises/ex7_rpc_show_version.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ''' 3 | Use Juniper's PyEZ and direct RPC to retrieve the XML for 'show version' from the Juniper SRX. 4 | 5 | Print out this returned XML as a string using 'etree.tostring()'. Parse the returned XML to 6 | retrieve the model from the device. Print this model number to the screen. 7 | ''' 8 | from __future__ import print_function, unicode_literals 9 | 10 | from lxml import etree 11 | from getpass import getpass 12 | 13 | from jnpr.junos import Device 14 | 15 | 16 | def main(): 17 | """Use Juniper PyEZ and direct RPC to retrieve the XML for 'show version' from the SRX.""" 18 | pwd = getpass() 19 | try: 20 | ip_addr = raw_input("Enter Juniper SRX IP: ") 21 | except NameError: 22 | ip_addr = input("Enter Juniper SRX IP: ") 23 | ip_addr = ip_addr.strip() 24 | 25 | juniper_srx = { 26 | "host": ip_addr, 27 | "user": "pyclass", 28 | "password": pwd 29 | } 30 | 31 | print("\n\nConnecting to Juniper SRX...\n") 32 | a_device = Device(**juniper_srx) 33 | a_device.open() 34 | 35 | # show version | display xml rpc 36 | # get-software-information 37 | show_version = a_device.rpc.get_software_information() 38 | print() 39 | print("Print show version XML out as a string (retrieved via PyEZ RPC):") 40 | print("-" * 20) 41 | print(etree.tostring(show_version, pretty_print=True).decode()) 42 | model = show_version.xpath("product-model")[0].text 43 | print() 44 | print("-" * 20) 45 | print("SRX Model: {}".format(model)) 46 | print() 47 | 48 | 49 | if __name__ == "__main__": 50 | main() 51 | -------------------------------------------------------------------------------- /xml_juniper_bonus/exercises/load_hostname.conf: -------------------------------------------------------------------------------- 1 | system { 2 | host-name test2; 3 | } 4 | -------------------------------------------------------------------------------- /xml_juniper_bonus/exercises/load_hostname.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | pynet-jnpr-srx1 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /xml_juniper_bonus/exercises/show_arp.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 00:62:ec:29:70:fe 4 | 10.220.88.1 5 | 10.220.88.1 6 | vlan.0 7 | 8 | 9 | 10 | 11 | 12 | c8:9c:1d:ea:0e:b6 13 | 10.220.88.20 14 | 10.220.88.20 15 | vlan.0 16 | 17 | 18 | 19 | 20 | 2 21 | 22 | -------------------------------------------------------------------------------- /xml_juniper_bonus/exercises/show_lldp.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | fe-0/0/7.0 4 | - 5 | Mac address 6 | 00:18:fe:1e:b0:20 7 | 24 8 | twb-sf-hpsw1 9 | 10 | 11 | --------------------------------------------------------------------------------