Привет, Хабр.
Здесь уже недавно были статьи про netmiko и автоматизацию управления коммутаторами Cisco.
Я хочу продолжить эту тему дальше в контексте взаимодействия сетевого отдела и отдела поддержки пользователей. (DSS digital site support как их называют)
Какие вопросы обычно возникают в процессе взаимодействия?
-
DSS необходимо подключить новых пользователей, или новые принтеры, или новые видеокамеры к коммутаторам и подобные вопросы.
-
DSS посылает запрос в сетевой отдел для настройки нескольких портов на коммутаторах Cisco для подключения устройств.
-
Сетевой отдел должен настроить несколько портов на коммутаторах Cisco в режиме access и соответствующий User vlan или Printer vlan.
Иногда на коммутаторах есть свободные, ранее настроенные порты, но у DSS нет информации для каких vlan эти порты настроены. Поэтому DSS посылает запрос в сетевой отдел.
Моё решение предлагает:
-
Автоматическую генерацию отчёта о всех портах коммутаторов Cisco в виде excel файла и рассылку этого отчёта в отдел поддержки.
Имея такую информацию специалисты поддержки могут сразу подключить новых пользователей если они видят свободные порты в коммутаторах и знают что порты в правильном vlan.
Решение осуществлено на python и может запускаться или каждую ночь по cron, или любой момент из jenkins. В jenkins это просто кнопка «создать отчет».
-
Специалист DSS может просто отредактировать Excel файл с новыми значениями vlan на требуемых портах и отослать этот файл на исполнение в jenkins и практически сразу сконфигурировать нужные vlan на нужных портах. Сетевой отдел не будет задействован. Эта задача будет ограничена только изменением vlan только на access портах. Порты trunk никак нельзя будет изменить с помощью этого скрипта.
Если вы не знакомы с jenkins, то это бесплатная графическая оболочка вместо командной строки и плюс логи, кто запускал, когда и каков результат.
Что необходимо? Виртуальная машина linux, ansible, python, netmiko, inventory file ansible в формате yaml.
И запускаться задача будет на любой группе свичей из inventory file.
Вот пример inventory file ansible:
all: vars: ansible_user: admin ansible_password: admin ansible_connection: ansible.netcommon.network_cli ansible_network_os: ios ansible_become: yes ansible_become_method: enable ansible_become_password: cisco ansible_host_key_auto_add: yes core_switch: hosts: core_switch1: ansible_host: 192.168.38.141 core_switch2: ansible_host: 192.168.38.142 sw: hosts: access_switch3: ansible_host: 192.168.38.143 access_switch4: ansible_host: 192.168.38.144 access_switch5: ansible_host: 192.168.38.145 access_switch6: ansible_host: 192.168.38.146 access_switch7: ansible_host: 192.168.38.147
Вот python программа, которая обращается ко всем коммутаторам из заданной группы и считывает информацию после выполнение команд «show interface status» «show cdp neighbor»
#!/usr/bin/python3 import yaml import argparse from netmiko import ConnectHandler import csv import subprocess # Function to parse command-line arguments def parse_arguments(): parser = argparse.ArgumentParser(description='Netmiko Script to Connect to Routers and Run Commands') parser.add_argument('--hosts_file', required=True, help='Path to the Ansible hosts file') parser.add_argument('--group', required=True, help='Group of routers to connect to from Ansible hosts file') return parser.parse_args() def ping_ip(ip_address): # Use ping command to check if it alive param = '-c' # for linux os # Build the command command = ['ping', param, '1', ip_address] try: # Execute the command subprocess.check_output(command, stderr=subprocess.STDOUT, universal_newlines=True) return "yes" except subprocess.CalledProcessError: return "no" # Main function def main(): # Parse command-line arguments args = parse_arguments() # Load the hosts file with open(args.hosts_file, 'r') as file: hosts_data = yaml.safe_load(file) # Extract global variables global_vars = hosts_data['all']['vars'] # Extract router details for the specified group if args.group not in hosts_data: print(f"Group {args.group} not found in hosts file.") return routers = hosts_data[args.group]['hosts'] comm1='sho int statu | beg Port' comm2='sho cdp nei | beg Device' output_filed = args.group + '_inter_des.csv' # output_filec = args.group + '_inter_cdp.csv' # STRd = "Hostname,IP_address,Interface,State,Description,Vlan" # with open(output_filed, "w", newline="") as out_filed: writer = csv.writer(out_filed) out_filed.write(STRd) out_filed.write('\n') STRc = "Hostname,IP_address,Interface,New_Description" # with ip with open(output_filec, "w", newline="") as out_filec: writer = csv.writer(out_filec) out_filec.write(STRc) out_filec.write('\n') # Connect to each router and execute the specified command for router_name, router_info in routers.items(): if ping_ip(router_info['ansible_host']) == "no": # check if host alive print( ' offline --------- ', router_name,' ',router_info['ansible_host']) continue else: print( ' online --------- ', router_name,' ',router_info['ansible_host']) # Create Netmiko connection dictionary netmiko_connection = { 'device_type': 'cisco_ios', 'host': router_info['ansible_host'], 'username': global_vars['ansible_user'], 'password': global_vars['ansible_password'], 'secret': global_vars['ansible_become_password'], } # Establish SSH connection connection = ConnectHandler(**netmiko_connection) # Enter enable mode connection.enable() # Execute the specified command outputd1 = connection.send_command(comm1) outputd2 = connection.send_command(comm2) # Print the output print(f" ------------ Output from {router_name} ({router_info['ansible_host']}):") print(f" ") lines = outputd1.strip().split('\n') lines = lines[1:] for line in lines: swi=router_name ipad= router_info['ansible_host'] por=line[:9].replace(' ', '') # port sta = line[29:41].replace(' ', '') # interface connected or notconnected des = line[10:28].replace(' ', '') # existing description vla = line[42:46].replace(' ', '') # vlan print("switch ",swi," port ",por, 'state ',sta," Descr ",des," vlan ", vla ) STR = swi + "," + ipad + "," + por +"," + sta +"," + des + "," + vla # +"," # with ip with open(output_filed, 'a') as f: f.write(STR) f.write('\n') lines1 = outputd2.strip().split('\n') lines1 = lines1[1:] # This correctly removes the first line (header) filtered_lines = lines1 try: first_empty_index = filtered_lines.index('') # Keep only the lines before the first empty line filtered_lines = filtered_lines[:first_empty_index] except ValueError: # No empty line found, do nothing pass lines1 = filtered_lines # cleaned_text print(' filtered_lines ', filtered_lines) for line in lines1: rlin1 = line[:16] dot_position = rlin1.find('.') rlin2 = rlin1[:dot_position] # remove domain name from name rlin = rlin2 + '|' + line[58:67] + '|' + line[68:] ndes = rlin.replace(' ', '') # remove all spaces por=line[17:33] por1 = por[0:2]+por[3:33] # remove 3rd char from port name por=por1.replace(' ', '') swi=router_name ipad= router_info['ansible_host'] print("switch ",swi," port ",por, " Descr ", ndes ) STRc = swi + "," + ipad + "," + por +"," + ndes # with ip with open(output_filec, 'a') as f: f.write(STRc) f.write('\n') print(f" ------------ end") connection.disconnect() # Disconnect from device output_filem = args.group + '_merg.csv' # with open(output_filed, mode='r') as file: reader = csv.DictReader(file) sw_inter_des_data = list(reader) # Read the sw_inter_cdp.csv file into a list of dictionaries with open(output_filec, mode='r') as file: reader = csv.DictReader(file) sw_inter_cdp_data = list(reader) # Create a lookup dictionary for sw_inter_cdp_data based on Hostname, IP_address, and Interface cdp_lookup = { (row['Hostname'], row['IP_address'], row['Interface']): row['New_Description'] for row in sw_inter_cdp_data } # Add the New_Description to sw_inter_des_data for row in sw_inter_des_data: key = (row['Hostname'], row['IP_address'], row['Interface']) row['New_Description'] = cdp_lookup.get(key, '') # Write the updated data to a new CSV file with open(output_filem, mode='w', newline='') as file: fieldnames = sw_inter_des_data[0].keys() writer = csv.DictWriter(file, fieldnames=fieldnames) writer.writeheader() writer.writerows(sw_inter_des_data) print("New CSV file with added New_Description column has been created as ", args.group , '_merg.csv') # Entry point of the script if __name__ == '__main__': main()
И вот итоговый csv файл:
Hostname,IP_address,Interface,State,Description,Vlan,New_Description access_switch3,192.168.38.143,Gi0/0,connected,PORT00,1,R3725|3725|Fas0/0 access_switch3,192.168.38.143,Gi0/1,connected,PORT11,1, access_switch3,192.168.38.143,Gi0/2,connected,002,1, access_switch3,192.168.38.143,Gi0/3,connected,003,1, access_switch3,192.168.38.143,Gi1/0,connected,sw2|Gig0/0,1,sw2||Gig0/0 access_switch3,192.168.38.143,Gi1/1,connected,011,20, access_switch3,192.168.38.143,Gi1/2,connected,12_012345678901123,22, access_switch3,192.168.38.143,Gi1/3,connected,13_012345678901234,23, access_switch4,192.168.38.144,Gi0/0,connected,sw1|Gig1/0,1,sw1||Gig1/0 access_switch4,192.168.38.144,Gi0/1,connected,,1, access_switch4,192.168.38.144,Gi0/2,connected,,1, access_switch4,192.168.38.144,Gi0/3,connected,,1, access_switch4,192.168.38.144,Gi1/0,connected,,1, access_switch4,192.168.38.144,Gi1/1,connected,,1, access_switch4,192.168.38.144,Gi1/2,connected,,1, access_switch4,192.168.38.144,Gi1/3,connected,,1,
Выходной файл можно дополнить столбцами mac address, ip address, vendor, lldp neighbor, uptime, downtime и др. Если у вас есть Cisco Call Manager и IP телефоны то можно дополнить столбцом с номером телефона, что значительно облегчит поиск телефонов.
Эта программа на тестовой стадии, я не проверял на стековых коммутаторах, у меня их нет под рукой, я проверял только на виртуальных коммутаторах Cisco. Также можно адаптировать для коммутаторов Juniper и Aruba.
Я буду рад услышать ваши любые комментарии.
ссылка на оригинал статьи https://habr.com/ru/articles/830558/
Добавить комментарий