Мой друг Netmiko. Часть 2: Три улучшения Python-скрипта
Продолжаю ковырять автоматизацию рутины на сети из Huawei коммутаторов. На этот раз изыскания, которые позволили сократить код в 3 раза, а именно: хосты и команды перенесены в отдельные файлы, пароль и имя пользователя больше не хранятся в открытом тексте. Есть демонстрация запуска скрипта.
Я лучше воспринимаю вещи визуально, поэтому представил задачу по оптимизации скрипта в графическом виде (см. ниже) - а именно отразил три основные задачи:
Применить inventory файл, в котором перечислить ip-адреса всех сетевых устройств, а не создавать словарь для каждого из них.
Переместить конфигурационные команды в отдельный файл и так же, как и inventory файл вызывать его с помощью функции.
Избавиться от хранения пароля и имени пользователя в скрипте.
Я также убрал команды верификации (Их, конечно, можно и оставить, и они будут работать из конфигурационного файла, но для настоящей задачи они не нужны).
В комментарии к предыдущему посту «Мой друг Netmiko» появился вопрос от пользователя anders_i о том, насколько можно масштабировать скрипт:
Тогда я использовал 4 виртуальных CloudEngine Huawei в eNSP. На этот раз я попробовал расширить их количество настолько, насколько хватит мощности Huawei MateBook X Pro 2021.
И расширить удалось до 8-ми. 9-ый запускаться отказался, оккупация 16GB ОЗУ составила 80%.
Обновленная топология выглядит так:
1. Создаю конфигурационный файл
Я предпочитаю редактор Nano (кто-то предпочитает Vim):
nano switch_file_config
В файле прописываю все команды из прошлого примера (команды могут быть любыми в зависимости от нужд) без пробелов/отступов. Важно указать команду return последней строкой. Как я понял, ее нужно прописывать вручную только при использовании конфигурационного файла – при прописывании команд в самом скрипке модуль Netmiko автоматически ее применяет и завершает SSH-соединение. Без return скрипт не сработает.
Список команд, перенесенные в отдельный файл:
Теперь весь предыдущий код:
for n in range (300,302):
print ("Creating VLAN " + str(n))
config_commands = [
'vlan ' + str(n),
'desc NETMIKO_VLAN ' + str(n),
'Commit'
]
output = ssh_connect.send_config_set(config_commands)
output = ssh_connect.send_config_set(
[
'interface range GE 1/0/9 GE 1/0/10',
'port trunk allow-pass vlan 300 301',
'commit'
]
)
Сокращаю до:
with open('switch_file_config') as f:
config_lines = f.read().splitlines()
print (config_lines)
и меняю строчку:
output = ssh_connect.send_config_set(config_lines)
Python функция open открывает указанный файл, читает его, разделяя на линии (форматируя) f.read().splitlines() и выводит на экран print(config_lines). Модуль Netmiko send_config_set теперь будет обращаться к этому файлу (config_lines), применяя команды из него.
2. Создаю inventory-файл с IP-адресами коммутаторов:
Начинаю с того же создания файла в редакторе Nano:
nano myswitches
Это файл будет содержать список (list) IP-адресов коммутаторов:
Здесь главное - убедиться, чтобы не осталось пробелов.
Сохраняю командой Ctr+X.
Для небольшого теста я создал python-файл, назвал его openfile.py.
Прописал в нем простой скрипт, который будет открывать файл с IP-адресами и выводить их на экран:
f = open ('myswitches')
for IP in f:
print (IP)
Запустил его:
Как видно, первая строчка осталась пустой, а значит для Python она будет выглядеть как [‘ ’,]. При попытке запуска такого файла Python выдаст ошибку, что не смог связаться с адресом назначения. Поэтому пустых строк лучше не допускать.
Я настроил дополнительные 4 CE-коммутатора в eNSP, дав им IP-адреса и настроив SSH-соединение:
Теперь сокращаю весь предыдущий код:
CE_1_BORDER = {
'device_type': 'huawei',
'ip': '7.7.7.1',
'username': 'vasyo1',
'password': '@ghjcnjnF358986'
}
CE_2 = {
'device_type': 'huawei',
'ip': '7.7.7.2',
'username': 'vasyo1',
'password': '@ghjcnjnF358986'
}
CE_3 = {
'device_type': 'huawei',
'ip': '7.7.7.3',
'username': 'vasyo1',
'password': '@ghjcnjnF358986'
}
CE_4 = {
'device_type': 'huawei',
'ip': '7.7.7.4',
'username': 'vasyo1',
'password': '@ghjcnjnF358986'
}
all_devices = [CE_1_BORDER, CE_2, CE_3, CE_4]
for device in all_devices:
ssh_connect = ConnectHandler(**device)
До:
with open('myswitches') as f:
ip_lines = f.read().splitlines()
print (ip_lines)
for device in ip_lines:
ip_address_of_device = device
CE = {
'device_type': 'huawei',
'ip': ip_address_of_device,
'username': username,
'password': password
}
ssh_connect = ConnectHandler(**CE)
output = ssh_connect.send_config_set(config_lines)
С синтаксисом открытия файла все понятно.
В словаре Netmiko больше не прописываю IP-адрес. Вместо этого теперь код будем обращаться за IP-адресами к переменной ip_address_of_device, которая равна переменной device, которая является частью цикла (loop) for, который читает ip_lines, которая является переменной, содержащей IP-адреса устройств в файле «myswitches».
3. Удаляю пароль и имя пользователя из скрипта
Небезопасно хранить имя пользователя и пароль, что называется, clear text:
CE = {
'device_type': 'huawei',
'ip': ip_address_of_device,
'username': 'vasyo1',
'password': '@ghjcnjnF358986'
}
Вряд ли в производственной сети такое допустимо.
Поэтому я удалю их из скрипта, заменив переменными: переделаю скрипт таким образом, чтобы скрипт вывел на экран сообщение о необходимости ввода имени пользователя, а после введенные значения сохранил в переменной. Для этого использую функцию input(). Функция input() для версии Python 2.x называется raw_input().
Функцию getpass() использую для ввода пароля.
username = input('Enter your SSH username: ')
password = getpass()
В словаре имя пользователя и пароль заменяю на переменные:
CE = {
'device_type': 'huawei',
'ip': ip_address_of_device,
'username': username,
'password': password
}
В итоге я получил улучшенный код, который сократился с 63 строк до 28 и стал более адаптированным для применения в реальной производственной практике. Выглядит он следующий образом (запуск скрипта на видео можно посмотреть на странице Huawei Форума ICT Club):
from getpass import getpass
from netmiko import ConnectHandler
username = input('Enter your SSH username: ')
password = getpass()
with open('switch_file_config') as f:
config_lines = f.read().splitlines()
print (config_lines)
with open('myswitches') as f:
ip_lines = f.read().splitlines()
print (ip_lines)
for device in ip_lines:
ip_address_of_device = device
CE = {
'device_type': 'huawei',
'ip': ip_address_of_device,
'username': username,
'password': password
}
ssh_connect = ConnectHandler(**CE)
output = ssh_connect.send_config_set(config_lines)
print(f"\n\n-------------- CE_{CE['ip']} --------------")
print(output)
print("-------------------- End -------------------")
Литература:
https://stackoverflow.com/questions/5563089/raw-input-function-in-python
https://pynet.twb-tech.com/blog/automation/netmiko.html
https://pyneng.readthedocs.io/en/latest/book/18_ssh_telnet/netmiko.html
https://github.com/ktbyers/netmiko
https://github.com/ktbyers/netmiko/blob/master/netmiko/ssh_dispatcher.py
Udemy.com - Python Network Programming for Network Engineers (Python 3) (David Bombal)
https://www.pythoncentral.io/pythons-range-function-explained