Продолжаю ковырять автоматизацию рутины на сети из Huawei коммутаторов. На этот раз изыскания, которые позволили сократить код в 3 раза, а именно: хосты и команды перенесены в отдельные файлы, пароль и имя пользователя больше не хранятся в открытом тексте. Есть демонстрация запуска скрипта.

Я лучше воспринимаю вещи визуально, поэтому представил задачу по оптимизации скрипта в графическом виде (см. ниже) - а именно отразил три основные задачи:

  1. Применить inventory файл, в котором перечислить ip-адреса всех сетевых устройств, а не создавать словарь для каждого из них.

  2. Переместить конфигурационные команды в отдельный файл и так же, как и inventory файл вызывать его с помощью функции.

  3. Избавиться от хранения пароля и имени пользователя в скрипте.

Я также убрал команды верификации (Их, конечно, можно и оставить, и они будут работать из конфигурационного файла, но для настоящей задачи они не нужны).

В комментарии к предыдущему посту «Мой друг 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