Главная

Saturday, 25 September 2021

Практикум с Netmiko.

Всем привет.

Продолжаем  автоматизировать рутинные процессы сетевого администрирования c Никитой Турковым. Сегодняшняя статья будет посвящена примерам и описанию работы скриптов, в которых также используем Netmiko и Python.

Обработка ошибок.

До сих пор мы рассматривали различные решения и не касались работы с ошибками при работе программы со стороны пользователя или сервера (в нашем случае - сетевое оборудование). 

Зачастую логика скрипта может быть написана правильно, но он все равно не отработает, предоставив специфический вывод об ошибке. В случае конфигурирования более 1-го сетевого узла, это может привести к выходу из программы и потери конечного ожидаемого результата.

Возможные причины сбоя вашего скрипта:

1. Некорректные аутентификационные данные.

2. Истек таймаут для подключения.

3. Прерывание передачи трафика.

4. Недоступность порта SSH со стороны сервера.

5. Различные другие ошибки.

Согласитесь, было бы удобно, каким-либо образом обрабатывать такие ошибки и сообщать пользователю о конкретной проблеме, а что даже наиболее важно - не прерывать выполнение программы далее. 

Для этого в Python есть конструкция try / except.

try:

  BLOCK

except CODE_ERROR as err:

    print("OS error: {0}".format(err))

То есть, в блоке "Try" мы пытаемся выполнить какой-либо код , он может вызвать ошибку, тогда мы пробуем ее обработать и получить о ней вывод - с помощью блока "Except". При этом скрипт продолжит свою работу далее, если это не последние шаги в программе.

Дополнительно есть операторы: 

"Break" - принудительный выход из цикла, соответственно далее выполняется сама программа. 

"Continue" - возвращает управление в начало цикла, следовательно позволяет пропустить оставшиеся строки в цикле и перейти к следующей итерации в нем же. 

Более подробно о подходе по обработке ошибок в официальном мануале.

Вернемся к нашей библиотеке Netmiko и рассмотрим скрипт, который будет проверять доступность сетевого устройства:

#!/usr/bin/env python

"Импорт необходимых библиотек"

from getpass import getpass

from netmiko import ConnectHandler

from netmiko.ssh_exception import NetMikoTimeoutException

from paramiko.ssh_exception import SSHException

from netmiko.ssh_exception import AuthenticationException

"Ввод аутентификационных данных в зашифрованном виде"

username = input("Enter your SSH username: ")

password = getpass()

"Открытие файла с командами и считывание их в переменную"

with open("commands_file") as f:

    commands_list = f.read().splitlines()

"Открытие файла со списком устройств и считывание их в переменную"

with open("devices_file") as f:

    devices_list = f.read().splitlines()

"Подключение к устройству из заданного списка в цикле FOR"

for devices in devices_list:

    print ("Connecting to device" " + devices)

    ip_address_of_device = devices

    ios_device = {

        "device_type": "cisco_ios",

        "ip": ip_address_of_device, 

        "username": username,

        "password": password

    }

"Попытка обработать ошибку" 

    try:

        net_connect = ConnectHandler(**ios_device)

    #Срабатывание исключений в случае неудачного создания соединения    

    except (AuthenticationException):

        print ("Authentication failure: " + ip_address_of_device)

        continue

    except (NetMikoTimeoutException):

        print ("Timeout to device: " + ip_address_of_device)

        continue

    except (EOFError):

        print ("End of file while attempting device " + ip_address_of_device)

        continue

    except (SSHException):

        print ("SSH Issue. Are you sure SSH is enabled? " + ip_address_of_device)

        continue

    except Exception as unknown_error:

        print ("Some other error: " + str(unknown_error))

        continue

    output = net_connect.send_config_set(commands_list)

    print (output)

Таким образом, в случае неудачного SSH-подключения, наш скрипт попробует обработать ошибку согласно заданной логике "except" и вывести на экран информацию. Введем заведомо неправильные значения логин/пароля и выведем на экран результат:

 


Для удобства показали как скрипт отработает в интерпретаторе с информацией о безуспешном входе на конкретное устройство. При этом дальнейшие подключения в цикле продолжили бы выполняться. 

Проверка версии обеспечения.

В современной сетевой инфраструктуре размещено большое количество различных коммутирующих и маршрутизирующих устройств, все они могут быть от различных вендоров со своими сетевыми ОС. Поэтому перед автоматической отправкой конфигурации, необходимо каким-то образом проходить валидацию, иначе команды попросту не будут приняты устройством, которому они не предназначались. 

Соответственно формируется задача: даны 3 различных конфигурационных файла, которые нужно отправить соответствующему оборудованию, с выводом на экран информации об отправке. 

"Импорт необходимых библиотек"

from getpass import getpass

from netmiko import ConnectHandler

from netmiko.ssh_exception import NetMikoTimeoutException

from paramiko.ssh_exception import SSHException

from netmiko.ssh_exception import AuthenticationException

"Ввод данных для подключения"

username = input("Enter your SSH username: ")

password = getpass()

"Считывание конфигураций в различные переменные"

with open("commands_file_switch") as f:

    commands_list_switch = f.read().splitlines()

with open("commands_file_router") as f:

    commands_list_router = f.read().splitlines()

with open("commands_file_phyrouter") as f:

    commands_list_phyrouter = f.read().splitlines()

"Считывание информации о сетевых устройствах"

with open("devices_file") as f:

    devices_list = f.read().splitlines()

"Запуск цикла с последовательным подключением к устройству"

for devices in devices_list:

    print ("Connecting to device" " + devices) #Вывод на экран

    ip_address_of_device = devices    

    "Формирования словаря для подключения"

        ios_device = {

        "device_type": "cisco_ios",

        "ip": ip_address_of_device, 

        "username": username,

        "password": password

    }

        "Обработка исключений"

        try:

        net_connect = ConnectHandler(**ios_device)

    except (AuthenticationException):

        print ("Authentication failure: " + ip_address_of_device)

        continue

    except (NetMikoTimeoutException):

        print ("Timeout to device: " + ip_address_of_device)

        continue

    except (EOFError):

        print ("End of file while attempting device " + ip_address_of_device)

        continue

    except (SSHException):

        print ("SSH Issue. Are you sure SSH is enabled? " + ip_address_of_device)

        continue

    except Exception as unknown_error:

        print ("Some other error: " + str(unknown_error))

        continue

    # Формирование списка сетевых ОС"

    list_versions = ["vios_l2-ADVENTERPRISEK9-M", 

                     "VIOS-ADVENTERPRISEK9-M",

                     "C1900-UNIVERSALK9-M",

                     "C3750-ADVIPSERVICESK9-M"

                     ]

    # Цикл по проверке сетевой ОС

    for software_ver in list_versions:

        print ("Checking for " + software_ver)

        output_version = net_connect.send_command("show version")

        int_version = 0 # Сброс переменной для цикла

        int_version = output_version.find(software_ver) # Проверка ОС на сетевом устройстве        

                # Условие о выводе информации по текущей ОС на устройстве

        if int_version > 0:

            print ("Software version found: " + software_ver)

            break

        else:

            print ("Did not find " + software_ver)

    "Если устройство одной из версий, то отправить соответствующий конфигурационный файл" 

        if software_ver == "vios_l2-ADVENTERPRISEK9-M":

        print ("Running " + software_ver + " commands")

        output = net_connect.send_config_set(commands_list_switch)

    elif software_ver == "VIOS-ADVENTERPRISEK9-M":

        print ("Running " + software_ver + " commands")

        output = net_connect.send_config_set(commands_list_router)

    elif software_ver == "C1900-UNIVERSALK9-M":

        print ("Running " + software_ver + " commands")

        output =      net_connect.send_config_set(commands_list_ph)

    elif software_ver == "C3750-ADVIPSERVICESK9-M":

        print ("Running " + software_ver + " commands")

        output = net_connect.send_config_set(commands_list_switch)  

    print (output) 

Результат отработки представлен ниже:

 


Скрипт подключился к устройству, взяв данные для SSH-сессии из файла, определил используемую ОС и отправил конфигурационный файл согласно заданному условию. Также в нем использовались уже изученные обработки исключений, которые позволили не прерываться процессу выполнения программы в случае ошибки, а продолжить свою работу.

Подключение без пароля.

Подключение к сетевому оборудованию посредством аутентификации с помощью логина/пароля не всегда удобно и безопасно, даже если мы используем модуль getpass(). Netmiko дружит с SSH-ключами и на простом примере, мы покажем как сделать такое подключение. 

Но для начала очень кратко по теории :

SSH-ключи представляют собой пару - закрытый и открытый ключ. Закрытый должен храниться в закрытом доступе у клиента, открытый отправляется на сервер и размещается в файле authorized_keys.

В нашем случае при запуске скрипта, он должен будет получить открытый ключ и авторизоваться за счет сравнения с закрытым ключом на стороне сетевого оборудования.

Процесс генерации ключей уже много лет всем известен, если тема для вас новая, то предлагаю ознакомиться по ссылке

Мы же перейдем непосредственно к практическому примеру с комментариями:

# Импорт библиотеки Netmiko

from netmiko import ConnectHandler

# Объявление переменной с путем до открытого SSH-ключа сервера

key_file = "~/.ssh/test_rsa"

# Объявление словаря с указанием типа аутентификации через ключ

cisco1 = {

    "device_type": "cisco_ios",

    "host": "cisco1.lasthop.io",

    "username": "testuser",

    "use_keys": True,

    "key_file": key_file,

}

# Создание подключения

with ConnectHandler(**cisco1) as net_connect:

    output = net_connect.send_command("show ip arp")

# Вывод на экран результата

print(f"\n{output}\n")

Удачи.

No comments:

Post a Comment

А что вы думаете по этому поводу?