Глава 2. Взаимодействие с сетевым устройством на нижнем уровне

В Главе 1, Обзор комплекта протокола TCP/IP и языка Python мы рассмотрели теорию и спецификации лежащих в основе сетевых протоколов взаимодействия, а также мы предприняли короткий обзор языка Python. В данной главе мы начнём погружаться глубже в само управление такими сетевыми устройствами. В частности, мы исследуем различные способы, в которых мы можем применять Python для программирования взаимодействия с наследуемыми сетевыми маршрутизаторами и коммутаторами.

Что я имею в виду под наследуемыми сетевыми маршрутизаторами и коммутаторами? Хотя в наши дни сложно себе представить какое бы то ни было сетевое оборудование без API (Application Program Interface, Интерфейса прикладного программирования), это общеизвестный факт, что многие сетевые устройства в наши дни разрабатываются без наличия API. Единственный способ управления ими состоит в применении CLI (Command Line Interfaces, Интерфейсов командной строки), которые первоначально разрабатывались имея в виду инженеров- людей. По мере роста общего числа сетевых устройств становится все тяжелее управлять ими вручную. Python имеет две величайшие библиотеки, которые могут помочь с такими задачами, поэтому данная глава рассмотрит Pexpect и Paramiko. В данной главе мы взглянем на следующие вопросы:

  • Вызовы CLI

  • Построение некоторой виртуальной лаборатории

  • Библиотека Pexpect Python

  • Библиотека Paramiko Python

  • Обратные стороны Pexpect и Paramiko

Вызовы CLI

На выставке Interop в Лас Вегасе в 2014г, руководитель BigSwitch Networks, Дуглас Мюррей показывал следующий слайд для иллюстрации произошедших изменений в DCN (Data Center Networking, Сетевых средах ЦОД) за 20 лет между 1993 и 2014:

 

Рисунок 1


Изменения сетевых сред ЦОД источник

Хотя такая демонстрация слайда может показаться не лицеприятной в отношении имеющихся производителей, точка зрения выбрана отменно. По его мнению единственное что изменилось в управлении маршрутизаторами и коммутаторами за 20 лет, так это только переход с протокола telnet на SSH. Почти в то же самое время мы наблюдаем в отрасли консенсус в связи с явной потребностью отказа от ручного, управляемого человеком CLI к автоматической, сосредоточенной в компьютере машинном API. Не будет ошибкой сказать что нам необходимо напрямую общаться с самим устройством при разработке сетевых проектов, привносить первоначальное доказательство концепций и первичного развёртывания такой топологии. Однако, как только мы выходим за рамки первоначального развёртывания, возникает потребность повторять одни и те же свободные от ошибок изменения снова и снова без устали. Это звучит как идеальная сфера применения для компьютеров и нашего любимого языка Python.

Возвращаясь обратно к нашему слайду, основной вызов состоит во взаимодействии между имеющимся маршрутизатором и самим администратором. Такой маршрутизатор ожидает, что администратор выполнит некую последовательность ручного ввода, которое требует интерпретации человеком. Например, вы должны ввести enable чтобы войти в некий привилегированный режим и пока вы получаете возвращаемое приглашение со знаком #, вы затем должны осуществить набор в configure terminal чтобы пройти в требующийся режим настроек. Тот же самый процесс может быть дальше расширен на режим взаимодействия с настройкой и режим настройки протокола маршрутизации. Это резко контрастирует с неким движимым вычислениями образом мыслей. Когда компьютер собирается выполнить некую отдельную задачу, скажем, поместить некий IP на какой- то интерфейс, он вначале конструктивно предоставляет всю необходимую информацию такому маршрутизатору за один раз, а затем ожидает отдельного yes или no от данного маршрутизатора для указания успешности данной задачи или отказа её исполнения.

Именно реализация и Pexpect, и Paramiko, является тем решением, которое способно подвергнуть обработке такой процесс взаимодействия как некий дочерний процесс и просматривать всё взаимодействие между самим процессом и его устройством назначения. Основываясь на возвращаемых значениях такой родительский процесс будет принимать решение о последующих действиях, если таковые необходимы.

Построение виртуальной лаборатории

Прежде чем мы погрузимся в сами пакеты, давайте рассмотрим варианты соединения в одно целое некоторой лаборатории для целей обучения. Как учит старая пословица: "Практика создаёт совершенство": нам необходима некая изолированная песочница чтобы безопасно делать ошибки, попробовать новые способы выполнения вещей и повторять некие шаги чтобы упрочить концепции, которые оказались не совсем ясными с первой попытки. Достаточно просто установить Python и необходимые пакеты для своего управляющего хоста, но как быть с теми маршрутизаторами и коммутаторами, которые мы намереваемся имитировать?

Для сбора воедино некоторой проверочной лаборатории у нас в основном имеются два варианта, причём каждый со своими преимуществами и недостатками:

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

    • Преимущества: Это самый простой способ перехода от лабораторных испытаний к промышленному применению, проще понять управляющим и приятелям инженерам, которые могут посмотреть и пощупать данное устройство.

    • Недостатки: Это относительно затратно, требует людских затрат на сборку и наладку и не очень гибко после построения.

  • Виртуальные устройства: при данном подходе выполняется эмуляция или имитация реальных сетевых устройств. Они выполняются либо самим производителем, либо имеющимся сообществом открытого исходного кода.

    • Преимущества: Это проще устанавливать, является более экономичным и может быстро изменяться.

    • Недостатки: Обычно это усечённая версия их физического аналога, причём порой имеются пробелы в их свойствах.

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

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

На переднем краю своей виртуальной лаборатории, помимо образов различных производителей я также применяю некую программу от Cisco с названием VIRL (Virtual Internet Routing Lab, Виртуальная лаборатория маршрутизации Интернета).

[Замечание]Замечание

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

Cisco VIRL

Я помню, что когда я впервые приступил к лабораторной подготовке к своему экзамену CCIE (Cisco Certified Internetwork Expert, сертифицированного специалиста по межсетевому оборудованию Cisco), я приобрёл некоторое использованное оборудование Cisco на eBay для изучения. Даже со скидкой каждый маршрутизатор и коммутатор стоил сотни долларов США и, чтобы сберечь средства, я приобретал действительно устаревшие маршрутизаторы Cisco начала 1980-х (поищите в предпочитаемом вами поисковике маршрутизаторы Cisco AGS чтобы хорошо посмеяться), которым существенно недоставало свойств и мощности даже для лабораторных стандартов. Насколько занимательным были обсуждения ситуации с членами семьи, когда я включил их (они реально были громкими), настолько же не забавным было соединение этих физических устройств воедино. Они были тяжёлыми и громоздкими, это было головной болью соединять все имеющиеся кабели и создавать отказ кабеля, который требовал реального отсоединения кабеля.

Ускоримся на несколько лет вперёд. Был создан Dynamip и я влюбился в то как просто было создавать сетевые сценарии. Всё что вам требуется - это образы iOS от Cisco, несколько тщательно выстроенных файлов топологии и вы можете легко построить некую виртуальную сеть, на которой вы сможете проверить свои знания. У меня была целая папка сетевых топологий, предварительно настроенных конфигураций и различных версий образов, которые вызывались в данном сценарии. Добавление некоего интерфейса GNS3 предоставляет всей настройке прекрасный обзор GUI. Теперь вы просто можете щёлкнуть и удалить свои соединения и устройства. Вы даже можете распечатать имеющуюся топологию сетевой среды для своего управляющего прямо из имеющейся панели проектирования GNS3.

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

[Замечание]Замечание

По состоянию на 1 января 2017, единственная лицензия на 20 узлов в персональной редакции доступна для приобретения за 199.99$ USD на год.

Даже при наличии денежных затрат, на мой взгляд данная платформа VIRL предлагает некоторые преимущества над прочими альтернативами:

  • Простота применения: в отдельную загрузку входят все образы для IOSv, IOS-XRv, CSR100v, NX-OSv, ASAv.

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

  • Путь миграции в облако: Данный проект предлагает путь логической миграции когда ваша имитация вырастает за рамки имеющегося у вас оборудования, в такое как Cisco dCloud, VIRL в Packet и Cisco DevNet.

  • Эмуляция соединения и области управления: данный инструментарий может имитировать задержки и неустойчивую синхронизацию (jitter), а также потерю пакетов для каждой линии с целью представления характеристик линий связи из реального мира. Имеется также генератор обмена области управления для внедрения имеющегося внешнего маршрутизатора.

  • Прочее: данный инструментарий также предоставляет некие забавные вещи, такие как проектирование топологии VM Maestro и управление имитацией, AutoNetkit для автоматической генерации настроек, а также управление имеющимся рабочим пространством пользователя если данный сервер находится в совместном использовании.

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

[Замечание]Замечание

И снова я хочу подчеркнуть важность наличия лаборатории, но она не обязана быть Cisco VIRL, по крайней мере для целей данной книги.

Уловки VIRL

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

  • VIRL использует два виртуальных интерфейса Ethernet для соединений. Самый первый интерфейс устанавливается как NAT для соединения с Интернетом данной хост машины, а второй используется для связи с локальным интерфейсом управления (в данном примере VMWare Fusion VMnet2). Я применяю некую виртуальную машину с аналогичной сетевой установкой чтобы исполнять свой собственный код Python, причём первый Ethernet я применяю для Интернета, а второй Ethernet соединяю с Vmnet2:

     

    Рисунок 2



  • Vmnet2 является некоторой пользовательской сетью, создаваемой для соединения данного хоста Ubuntu с имеющейся виртуальной машиной VIRL

     

    Рисунок 3



  • В опции Topology Design я устанавливаю значение параметра Management Network в Shared Flat Network чтобы применять VMnet2 в качестве сети управления в данных виртуальных маршрутизаторах:

     

    Рисунок 4



  • В настройке данного узла у вас имеется вариант для статической настройки данным IP управления. Я всегда устанавливаю данный IP адрес статически вместо его динамического назначения самим программным обеспечением, таким образом я могу легко подключаться к этому устройству без проверок:

     

    Рисунок 5



Cisco DevNet и dCloud

Cisco предоставляет два других исключительных, на момент написания свободных, метода практики взаимодействия с API различным парням Cisco. Оба этих инструмента требуют регистрации в интернет ресурсе с подключением к Cisco. Они оба действительно хороши, в особенности с точки зрения стоимости (они свободны для доступа!). Мне трудно представить что эти средства интернет доступа останутся свободными на протяжении длительного периода времени, это моя вера до некоторой степени, такие средства требуют оплаты за пользование или должны перерастать в некую большую инициативу, которая требует некого сбора, однако мы можем получить преимущество от них пока они доступны без какой либо платы.

Первый инструмент это DevNet, который содержит управляемые обучающие треки, полную документацию и удалённые лаборатории песочниц, помимо прочих достоинств. некоторые из этих лабораторий всегда включены, в то время как некоторые требуют резервации и доступны в зависимости от загруженности. По моему опыту работы с DevNet некоторые ссылки и какая- то документация не соответствуют текущему времени, но можно быстро отыскать самые последние обновления. В быстро меняющейся отрасли, такой как разработка программного обеспечения, это нечто ожидаемое. DevNet несомненно некий инструмент, который должен дать полное преимущество вне зависимости от того имеет ли вы локально исполняемый хост VIRL или нет:

 

Рисунок 6



Другим доступным вариантом лаборатории для Cisco является Cisco dCloud. Вы можете представлять себе dCloud как запущенный на сервере других людей VIRL без наличия управления или оплаты за эти ресурсы. Кажется, что Cisco трактует dCloud и как отдельный продукт, и как некое расширение VIRL. Например, в том случае когда у вас нет возможности исполнять несколько экземпляров IOX-XR или NX-OS локально, вы можете применять dCloud для расширения вашей локальной лаборатории. Это относительно новый инструмент, но на него несомненно стоит взглянуть:

 

Рисунок 7



GNS3

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

 

Рисунок 8



Как уже упоминалось, GNS3 это то, что большинство из нас привыкли изучать для тестирования и практической лабораторной работы. Он действительно вырос начиная с ранних дней до некоторого зрелого коммерческого продукта. Инструменты Cisco, VIRL, DevNet и dCloud содержат только технологию Cisco. Они предоставляют способ для взаимодействия за пределами данной лаборатории, однако они не настолько просты и представляют только виртуализацию устройств других производителей, обитающих непосредственно в среде моделирования. GNS3 является нейтральным производителем и может содержать платформы прочих производителей непосредственно, либо делая некий клон определённого образа (такого как Arista vEOS), либо путём запуска прочих образов через другие гипервизоры (такие, как эмулятор Juniper Olive). GNS3 не обладает широтой и глубиной проекта Cisco VIRL, но так как он может исполнять различные вариации технологий Cisco, я применял его много раз, когда мне нужно было внедрять технологии прочих производителей в своей лаборатории помимо образов Cisco.

Имеются также и платформы виртуализации и прочих производителей, например, Arista vEOS, Juniper vMX и vSRX, которые вы можете применять как отдельные виртуальные устройства. Они являются великолепными добавочными инструментами для проверочных платформ специфических свойств, таких как наличие различий между имеющимися версиями API. Многие из них предлагаются как платные продукты на рынке поставщиков облачных услуг и предлагают идентичные свойства что и их физические профили.

Библиотека Python Pexpect

Pexpect - это чистый модуль Python для порождения дочерних приложений, управления ими и ответа на ожидаемые шаблоны в их выходе. Pexpect работает, как Expect Дона Лайба. Pexpct позволяет вашему сценарию порождать дочернее приложение и управлять им, как если бы человек сам вводил команды. - Pexpect Read the Docs.

Как и первоначальный модуль Expect Дона Лайба, Pexpect запускает, или порождает (spawn) другой процесс и осуществляет наблюдение за ним, чтобы управлять всем взаимодействием. В отличие от первоначального Expect, он целиком написан на Python, который не требует компиляции расширений TCL или C. Это позволяет нам применять знакомый из Python синтаксис и его богатую стандартную библиотеку в своём коде.

Установка

Вся установка пакетов pip и pexpect достаточно незатейлива:


sudo apt-get install python-pip
sudo apt-get install python3-pip
sudo pip3 install pexpect
sudo pip install pexpect
 	   
[Замечание]Замечание

Я применяю pip3 для установки пакетов Python 3 и pip для установки своего интерпретатора по умолчанию Python 2.

Для того чтобы убедиться в его доступности выполним быстрый тест:


>>> import pexpect
>>> dir(pexpect)
['EOF', 'ExceptionPexpect', 'Expecter', 'PY3',
  'TIMEOUT', '__all__', '__builtins__', '__cached__',
  '__doc__', '__file__', '__loader__', '__name__',
  '__package__', '__path__', '__revision__',
  '__spec__', '__version__', 'exceptions', 'expect',
  'is_executable_file', 'pty_spawn', 'run', 'runu',
  'searcher_re', 'searcher_string', 'spawn',
  'spawnbase', 'spawnu', 'split_command_line', 'sys',
  'utils', 'which']
>>>
 	   

Обзор Pexpect

Давайте рассмотрим как вы бы выполняли взаимодействие со своим маршрутизатором, если бы вы подключились к своему устройству через Telnet:


echou@ubuntu:~$ telnet 172.16.1.20
Trying 172.16.1.20...
Connected to 172.16.1.20.
Escape character is '^]'.
<пропуск>
User Access Verification

Username: cisco
Password:
 	   

Я применяю VIRL AutoNetkit для автоматического создания начальной настройки своих маршрутизаторов, которые вырабатывают по умолчанию имя пользователя cisc и его пароль cisco. отметим, что данный пользователь уже находится в привилегированном режиме:


iosv-1#sh run | i cisco
enable password cisco
username cisco privilege 15 secret 5 $1$Wiwq$7xt2oE0P9ThdxFS02trFw.
 password cisco
 password cisco
iosv-1#
 	   

также отметим, что данная автоматическая настройка создаёт доступ vty и для telnet, и для SSH:


line vty 0 4
 exec-timeout 720 0
 password cisco
 login local
 transport input telnet ssh
 	   

Давайте рассмотрим пример Pexpect с применением интерактивной оболочки Python:


Python 3.5.2 (default, Nov 17 2016, 17:05:23)
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import pexpect
>>> child = pexpect.spawn('telnet 172.16.1.20')
>>> child.expect('Username')
0
>>> child.sendline('cisco')
6
>>> child.expect('Password')
0
>>> child.sendline('cisco')
6
>>> child.expect('iosv-1#')
0
>>> child.sendline('show version | i V')
19
>>> child.expect('iosv-1#')
0
>>> child.before
b'show version | i VrnCisco IOS Software, IOSv Software (VIOS-ADVENTERPRISEK9-M), Version 15.6(2)T, RELEASE SOFTWARE (fc2)rnProcessor board ID 9MM4BI7B0DSWK40KV1IIRrn'
>>> child.sendline('exit')
5
>>> exit()
 	   
[Замечание]Замечание

Начиная с версии 4.0 Pexpect, вы можете исполнять Pexpect на некоторой платформе Windows. Однако, как отмечается в документации, исполнение Pexpect в Windows должно в настоящее время рассматриваться как экспериментальное.

Следует отметить один момент в предыдущем интерактивном примере, а именно, как вы можете увидеть, Pexpect порождает некий дочерний процесс и наблюдает за ним в интерактивном режиме. Имеются два важных метода, отображённых в данном примере, expect() и sendline(). также имеется некий метод с наименованием send(), но sendline() содержит перевод строки, что аналогично нажатию клавиши Enter в конце вашей строки в вашем предыдущем сеансе telnet. С точки зрения маршрутизатора, это в точности соответствует набору текста из некоторого Терминала. Другими словами, мы хитрим со своим маршрутизатором, чтобы он воспринял данное действие как взаимодействие с неким оператором, который действительно общается с ним через некий компьютер.

Для данного текста набираемого таким дочерним приложением будут установлены свойства before и after. Значение свойства before будет установлено на тот текст, который был напечатан нашим дочерним приложением. Значение строки after будет содержать тот текст, который соответствует вашему шаблону ожидания (определённому в expect). В нашем случае текст before будет установлен в тот вывод, который произошёл между двумя соответствиями ожидания (iosv-1#) и содержащим команду show version. Значение текста after содержит приглашение от имени хоста данного маршрутизатора:


Python 3.5.2 (default, Nov 17 2016, 17:05:23)
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> child.sendline('show version | i V')
19
>>>  child.expect('iosv-1#')
0
>>>  child.before
b'show version | i VrnCisco IOS Software, IOSv Software (VIOS-ADVENTERPRISEK9-
M), Version 15.6(2)T, RELEASE SOFTWARE (fc2)rnProcessor board ID
9MM4BI7B0DSWK40KV1IIRrn'
>>>  child.after
b'iosv-1#'
 	   

Что произойдёт, если вы ожидаете не правильное имя? Например, если вы наберёте в нижнем регистре всё username, вместо Username после того как вы породили своё дочернее приложение, тогда ваш процесс Pexpext будет ожидать от своего дочернего процесса строку username. В данном случае ваш процесс Pexpect просто повиснет, так как данное слово username никогда не будет возвращено со стороны маршрутизатора. Данный сеанс со временем завершится по таймауту, либо есть возможность выхода вручную через Ctrl+C.

Данный метод expect() ожидает своего дочернего приложения чтобы вернуть некую заданную строку, поэтому если в предыдущем примере вы хотите собрать символы u и в нижнем, и в верхнем регистре, вы можете применить такое выражение:


>>> child.expect('[Uu]sername')
 	   

Квадратные скобки обслуживают некую операцию or (или), которая сообщает своему дочернему приложению ожидать символ u либо в нижнем, либо в верхнем регистре, за которым следует строка 'sername'. То что мы сообщаем своему процессу, так это то, что мы будем принимать либо 'Username', либо 'username' в качестве ожидаемой строки.

[Замечание]Замечание

Для получения дополнительной информации по регулярным выражениям Python обратитесь к https://docs.python.org/3.5/library/re.htm.

Данный метод expect() также может содержать некий перечень вариантов вместо некоторой отдельной строки; могут также быть сами по себе регулярным выражением. Возвращаясь обратно к предыдущему примеру, вы можете применять следующий список вариантов для сбора двух различных возможных строк:


>>> child.expect(['Username', 'username'])
 	   

Вообще говоря, применяйте определённое регулярное выражение для некоторой отдельной строки ожидания, когда вы можете заполнить отличающиеся имена хоста в некотором регулярном выражении, поскольку применяя все возможные варианты вы сможете отлавливать совершенно различные отклики от своего маршрутизатора, например, отказ в приёме пароля. Скажем, если вы применяете некоторые различные пароли для своей регистрации, вы пожелаете помимо самого приглашения устройства отлавливать и % Login invalid.

Одно важное отличие регулярных выражений (RE) Pexpect и RE Python состоит в том, что соответствия Pexpect являются не жадными, что означает, что они будут соответствовать как можно меньшему общему числу применяемых специальных символов. Так как Pexpect исполняет регулярные выражения в потоке, вы не можете заглядывать вперёд, так как создающий этот поток дочерний процесс может быть не завершён. Это означает, что имеющийся специальный символ с названием $ для обозначения конца соответствия данной строки бесполезен, .+ никогда не будет возвращать символы, а шаблон .* будет соответствовать самому минимальному из допустимых. В общем случае просто имейте это в виду и будьте настолько конкретны в строках ожидания, насколько это возможно. {Прим. пер.: не умничайте!}

Давайте рассмотрим такой вариант:


>>> child.sendline('show run | i hostname')
22
>>> child.expect('iosv-1')
0
>>> child.before
b'show run | i hostnamernhostname '
>>>
 	   

Хмм... здесть что- то не так. Сравним со своим терминальным выводом before; тот вывод, который вы бы ожидали должен бы быть hostname iosv-1:


iosv-1#show run | i hostname
hostname iosv-1
iosv-1#
 	   

Более пристальный просмотр этой ожидаемой строки выявляет искомую ошибку. В данном случае вы пропустили хэш- символ (#) позади самого вызываемого имени хоста iosv-1. таким образом наше дочернее приложение воспринимает вторую часть данной возвращаемой строки как свою ожидаемую строку:


>>> child.sendline('show run | i hostname')
22
>>> child.expect('iosv-1#')
0
>>> child.before
b'show run | i hostnamernhostname iosv-1rn'
>>>
 	   

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

Наша первая программа ожидания

Наша первая программа расширяет то что мы сделали в самом последнем разделе некоторым дополнительным кодом:


#!/usr/bin/python3
import pexpect
devices = {'iosv-1': {'prompt': 'iosv-1#', 'ip': '172.16.1.20'}, 'iosv-2': {'prompt': 'iosv-2#', 'ip': '172.16.1.21'}}
username = 'cisco'
password = 'cisco'

for device in devices.keys():
  device_prompt = devices[device]['prompt']
  child = pexpect.spawn('telnet ' + devices[device]['ip'])
  child.expect('Username:')
  child.sendline(username)
  child.expect('Password:')
  child.sendline(password)
  child.expect(device_prompt)
  child.sendline('show version | i V')
  child.expect(device_prompt)
  print(child.before)
  child.sendline('exit')
 	   

В строке 5 мы применяем встроенный словарь:


devices = {'iosv-1': {'prompt': 'iosv-1#', 'ip': '172.16.1.20'}, 'iosv-2': {'prompt': 'iosv-2#', 'ip': '172.16.1.21'}}
 	   

такой встроенный словарь позволяет нам ссылаться на одно и то же устройство (например, iosv-1) при помощи соответствующего IP адреса и символа приглашения. Затем мы можем применять эти значения для своего метода expect() позже в своём цикле.

Наш вывод напечатает на нашем экране выдачу 'show version | i V' для всех имеющихся у нас устройств:


$ python3 chapter2_1.py
 b'show version | i VrnCisco IOS Software, IOSv Software (VIOS-ADVENTERPRISEK9-M), Version 15.6(2)T, RELEASE SOFTWARE (fc2)rnProcessor board ID 9MM4BI7B0DSWK40KV1IIRrn'
b'show version | i VrnCisco IOS Software, IOSv Software (VIOS-ADVENTERPRISEK9-M), Version 15.6(2)T, RELEASE SOFTWARE (fc2)rn'
 	   

Дополнительная функциональность Pexpect

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

Если у вас имеется медленное соединение с вашим удалённым устройством, имейте в виду, что значение таймаута метода expect() по умолчанию составляет 30 секунд, и вы можете увеличить его значение через аргумент timeout:


>>> child.expect('Username', timeout=5)
 	   

Вы можете выбрать передачу исполненной команды обратно данному пользователю с помощью метода interact(). Это полезно только когда вы хотите автоматизировать определённые части вашей начальной задачи:


>>> child.sendline('show version | i V')
19
>>> child.expect('iosv-1#')
0
>>> child.before
b'show version | i VrnCisco IOS Software, IOSv Software (VIOS-ADVENTERPRISEK9-M), Version 15.6(2)T, RELEASE SOFTWARE (fc2)rnProcessor board ID 9MM4BI7B0DSWK40KV1IIRrn'
>>> child.interact()
iosv-1#show run | i hostname
hostname iosv-1
iosv-1#exit
Connection closed by foreign host.
>>>
 	   

Вы можете получить массу информации о своём объекте child.spawn, распечатав её в формате строки:


>>> str(child)
"<pexpect.pty_spawn.spawn object at 0x7fb01e29dba8>ncommand:/usr/bin/telnetnargs: ['/usr/bin/telnet', '172.16.1.20']nsearcher: Nonenbuffer (last 100 chars): b''nbefore (last 100 chars): b'NTERPRISEK9-M), Version 15.6(2)T, RELEASE SOFTWARE (fc2)\r\nProcessor board ID 9MM4BI7B0DSWK40KV1IIR\r\n'nafter: b'iosv-1#'nmatch: <_sre.SRE_Match object; span=(164, 171), match=b'iosv-1#'>nmatch_index: 0nexitstatus: 1nflag_eof: Falsenpid: 2807nchild_fd: 5nclosed: Falsentimeout: 30ndelimiter: <class 'pexpect.exceptions.EOF'>nlogfile: Nonenlogfile_read: Nonenlogfile_send: Nonenmaxread: 2000nignorecase: Falsensearchwindowsize: Nonendelaybeforesend: 0.05ndelayafterclose: 0.1ndelayafterterminate: 0.1"
>>>
 	   

Наиболее полезным инструментом для отладки Pexpext является регистрация его вывода в некий файл:


>>> child = pexpect.spawn('telnet 172.16.1.20')
>>> child.logfile = open('debug', 'wb')
>>>
 	   
[Замечание]Замечание

Для Python 2 применяйте child.logfile = open('debug', 'w'). Python 3 испольует по умолчанию строку байт. Для получения дополнительной информации по свойствам Pexpext обращайтесь к https://pexpect.readthedocs.io/en/stable/api/index.html.

Pexpect и SSH

Если вы попробуете применить предыдущий пример telnet и просто подключитесь к некоторому сеансу ssh, вы можете быть очень расстроены. Вам всегда придётся включать в данный сеанс имя своего пользователя, новый запрос ключа ssh и многое другое. Имеется множество вариантов заставить работать сеансы ssh, но к счастью, Pexpext имеет некий подкласс с названием pxssh, который предназначается для установки соединений SSH. Данный класс добавляет методы регистрации, выхода и различные хитрости для обработки множества ситуаций в процессе данной регистрации ssh. Все процедуры в основном одни и те же, за исключением login() и logout().


>>> from pexpect import pxssh
>>> child = pxssh.pxssh()
>>> child.login('172.16.1.20', 'cisco', 'cisco', auto_prompt_reset=False)
True
>>> child.sendline('show version | i V')
19
>>> child.expect('iosv-1#')
0
>>> child.before
b'show version | i VrnCisco IOS Software, IOSv Software (VIOS-ADVENTERPRISEK9-M), Version 15.6(2)T, RELEASE SOFTWARE (fc2)rnProcessor board ID 9MM4BI7B0DSWK40KV1IIRrn'
>>> child.logout()
>>>
 	   

Отметим передаваемый в метод login() аргумент 'auto_prompt_reset=False'. По умолчанию pxssh применяет приглашение оболочки для синхронизации своего вывода. Однако так как он применяет для большинства bash и Csh параметр PS1, это может приводить к ошибкам на сетевых устройствах Cisco или прочих производителей.

Собираем всё воедино для Pexpect

На завершающем этапе давайте поместим всё что вы изучили относительно Pexpect в неий сценарий. Размещение всего кода в некотором сценарии делает более простым его применение в промышленных средах, а также для совместного использования этого кода с вашими коллегами.

[Замечание]Замечание

Вы можете выгрузить этот сценарий с репозитория GitHub данной книги, а также просмотрев вывод, создаваемый данным сценарием как результат исполнения команд.


#!/usr/bin/python3

import getpass
from pexpect import pxssh

devices = {'iosv-1': {'prompt': 'iosv-1#', 'ip': '172.16.1.20'}, 'iosv-2': {'prompt': 'iosv-2#', 'ip': '172.16.1.21'}} 
commands = ['term length 0', 'show version', 'show run']

username = input('Username: ')
password = getpass.getpass('Password: ')

# Начало основного цикла для устройств
for device in devices.keys():
    outputFileName = device + '_output.txt'
    device_prompt = devices[device]['prompt']
    child = pxssh.pxssh()
    child.login(devices[device]['ip'], username.strip(), password.strip(), auto_promp t_reset=False)
    # Начало основного цикла для команд и записи вывода
    with open(outputFileName, 'wb') as f:
        for command in commands:
            child.sendline(command)
            child.expect(device_prompt)
            f.write(child.before)

    child.logout()
 	   

Данный сценарий ещё расширяет нашу первую программу Pexpext следующими дополнительными функциями:

  • Применение SSH вместо telnet

  • Использование множества команд вместо всего одной посредством создания некоторого списка команд (строка 7) и цикла по этим командам (начинающегося в строке 20)

  • Он запрашивает у своего пользователя имя пользователя и пароль вместо жёсткой прошивки его в самом сценарии

  • Он записывает весь вывод в некий файл для последующего анализа

[Замечание]Замечание

В Python 2 применяйте raw_input() вместо input() для приглашения ввода имени пользователя. Также для файлового режима используйте w вместо wb.

Библиотека Python Paramiko

Paramiko является некоторой реализацией Python протокола SSHv2. В точности как и подкласс Pexpext pxssh, Paramiko упрощает взаимодействие SSHv2 с удалённым устройством. В отличии от pxssh.

, Paramiko сосредоточен только на SSHv2 и предоставляет как операции клиента, так и операции сервера.

Paramiko является клиентом SSH низкого уровня в имеющейся инфраструктуре автоматизации верхнего уровня Ansible для его сетевых модулей. Мы рассмотрим Ansible в последующих главах, поэтому давайте вначале взглянем на библиотеку Paramiko.

Установка Paramiko

Установка Paramiko достаточно прямолинейна и выполняется через PIP. Однако, имеется жёсткая привязка к Криптографической библиотеке. Данная библиотека предоставляет алгоритмы шифрования на C низкого уровня для самого протокола SSH.

[Замечание]Замечание

Все необходимые инструкции по установке для Windows, Mac и прочих предпочитаемых вами Linux можно найти по адресу https://cryptography.io/en/latest/installation/.

Мы продемонстрируем установку для своей виртуальной машины Ubuntu 16.04 в последующем выводе. Приводимый ниже вывод отражает этапы установки а также успешный импорт Paramiko через интерактивное приглашение Python.

В Python 2:


sudo apt-get install build-essential libssl-dev libffi-dev python-dev
sudo pip install cryptography
sudo pip install paramiko
$ python
Python 2.7.12 (default, Nov 19 2016, 06:48:10)
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import paramiko
>>> exit()
 	   

В Python 3:


sudo apt-get install build-essential libssl-dev libffi-dev python3-dev
sudo pip3 install cryptography
sudo pip3 install paramiko
$ python3
Python 3.5.2 (default, Nov 17 2016, 17:05:23)
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import paramiko
>>>
 	   

Обзор Paramiko

Давайте рассмотрим короткий пример с использованием интерактивной оболочки Python 3:


>>> import paramiko, time
>>> connection = paramiko.SSHClient()
>>> connection.set_missing_host_key_policy(paramiko.AutoAddPolicy())
>>> connection.connect('172.16.1.20', username='cisco', password='cisco',
look_for_keys=False, allow_agent=False)
>>> new_connection = connection.invoke_shell()
>>> output = new_connection.recv(5000)
>>> print(output)
b"rn**************************************************************************rn*
IOSv is strictly limited to use for evaluation, demonstration and IOS *rn*
education. IOSv is provided as-is and is not supported by Cisco's *rn* Technical
Advisory Center. Any use or disclosure, in whole or in part, *rn* of the IOSv
Software or Documentation to any third party for any *rn* purposes is expressly
prohibited except as otherwise authorized by *rn* Cisco in writing.
IOSv строго ограничен на применение для оценки, демонстрации возможностей и IOS *rn*
обучения. IOSv предоставляется как-есть и не поддерживается Cisco's *rn* Technical
Advisory Center. Любое применение или нарушение конфиденциальности, целиком или частично,  
программного обеспечения или документации *rn* IOSv любому третьему лицу запрещено 
за исключением случаев с письменным разрешением *rn* Cisco.
*rn**************************************************************************rniosv-
1#"
>>> new_connection.send("show version | i Vn")
19
>>> time.sleep(3)
>>> output = new_connection.recv(5000)
>>> print(output)
b'show version | i VrnCisco IOS Software, IOSv Software (VIOS-ADVENTERPRISEK9-M), Version 15.6(2)T, RELEASE SOFTWARE (fc2)rnProcessor board ID 9MM4BI7B0DSWK40KV1IIRrniosv-1#'
>>> new_connection.close()
>>>
 	   
[Замечание]Замечание

Приводимая функция time.sleep() вставляет временную задержку чтобы гарантировать перехват вывода. Это в особенности полезно для более медленных сетевых соединений или какого- то занятого устройства. Данная команда не обязательна, но рекомендуется по обстоятельствам.

Даже если вы видите работу Paramiko впервые, привлекательность Python и его ясный синтаксис означают, что вы можете сделать достаточно хорошее предположение с целью обучения в том что пытается выполнить данная программа:


>>> import paramiko
>>> connection = paramiko.SSHClient()
>>> connection.set_missing_host_key_policy(paramiko.AutoAddPolicy())
>>> connection.connect('172.16.1.20', username='cisco', password='cisco', look_for_keys=False, allow_agent=False)
 	   

Первые четыре строки создают некий экземпляр имеющегося в Paramiko класса SSHClient. Следующая строка устанавливает ту политику, которую должен применять данный клиент когда имя данного хоста сервера хоста SSH, в нашем случае iosv-1, не предоставлено ни в каких ключах системного хоста или используемых приложений. В данном случае мы просто автоматически добавляем необходимый ключ к объекту HostKeys своего приложения. В данный момент, если вы зарегистрированы в своём маршрутизаторе, вы обнаружите дополнительный сеанс регистрации из Paramiko:


iosv-1#who
 Line User Host(s) Idle Location
*578 vty 0 cisco idle 00:00:00 172.16.1.1
 579 vty 1 cisco idle 00:01:30 172.16.1.173
Interface User Mode Idle Peer Address
iosv-1#
 	   

Следующие несколько строк исполняют некую новую интерактивную оболочку в соединении и какой- то повторяющийся шаблон отправки команд и получения их вывода. По окончанию мы закрываем своё соединение.

Зачем нам нужно исполнять некую интерактивную оболочку вместо использования другого метода с названием exec_command()? К сожалению, exec_command() в Cisco IOS позовляет только однц команду Рассмотрим следующий пример с использованием exec_command() для своего соединения:


>>> connection.connect('172.16.1.20', username='cisco', password='cisco',
look_for_keys=False, allow_agent=False)
>>> stdin, stdout, stderr = connection.exec_command('show version | i V')
>>> stdout.read()
b'Cisco IOS Software, IOSv Software (VIOS-ADVENTERPRISEK9-M), Version 15.6(2)T, RELEASE SOFTWARE (fc2)rnProcessor board ID 9MM4BI7B0DSWK40KV1IIRrn'
>>>
 	   

Всё работает прекрасно, однако если вы посмотрите на общее число сеансов с устройством Cisco, вы отметите, что данное соединение было сброшено самим устройством Cisco без какого- либо закрытия данного соединения:


iosv-1#who
 Line User Host(s) Idle Location
*578 vty 0 cisco idle 00:00:00 172.16.1.1
Interface User Mode Idle Peer Address
iosv-1#
 	   

Более того, exec_command() вернёт некую ошибку сеанса не являющегося активным:


>>> stdin, stdout, stderr = connection.exec_command('show version | i V')
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
 File "/usr/local/lib/python3.5/dist-packages/paramiko/client.py", line 435, in
exec_command
 chan = self._transport.open_session(timeout=timeout)
 File "/usr/local/lib/python3.5/dist-packages/paramiko/transport.py", line 711, in open_session timeout=timeout)
 File "/usr/local/lib/python3.5/dist-packages/paramiko/transport.py", line 795, in open_channel raise SSHException('SSH session not active')
paramiko.ssh_exception.SSHException: SSH session not active
>>>
 	   
[Замечание]Замечание

Доступная библиотека Netmiko от Кирка Байера является библиотекой Python с открытым исходным кодом, и она упрощает управление SSH сетевыми устройствами. Чтобы прочесть о ней, обратитесь к статье https://pynet.twbtech.com/blog/automation/netmiko.html, а за исходным кодом к https://github.com/ktbyers/netmiko.

Что произойдёт если вы не очистите своё буфер приёма? Весь вывод просто останется в заполненном буфере и перепишет его поверх:


>>> new_connection.send("show version | i Vn")
19
>>> new_connection.send("show version | i Vn")
19
>>> new_connection.send("show version | i Vn")
19
>>> new_connection.recv(5000)
b'show version | i VrnCisco IOS Software, IOSv Software (VIOS-ADVENTERPRISEK9-M), Version 15.6(2)T, RELEASE SOFTWARE (fc2)rnProcessor board ID 9MM4BI7B0DSWK40KV1IIRrniosv-1#show version | i VrnCisco IOS Software, IOSv Software (VIOS-ADVENTERPRISEK9-M), Version 15.6(2)T, RELEASE SOFTWARE (fc2)rnProcessor board ID 9MM4BI7B0DSWK40KV1IIRrniosv-1#show version | i VrnCisco IOS Software, IOSv Software (VIOS-ADVENTERPRISEK9-M), Version 15.6(2)T, RELEASE SOFTWARE (fc2)rnProcessor board ID 9MM4BI7B0DSWK40KV1IIRrniosv-1#'
>>>
 	   

Для полных согласованности и определённости вывода мы будем изымать весь вывод из своего выходного буфера после каждого исполнения какой- либо команды.

Наша первая программа Paramiko

Наша первая программа будет применять ту же самую структуру имеющейся программы Pexpect при обходе по циклу некоторого списка устройств и команд с тем исключением, что будет применять Pexpect вместо Paramiko. Это даст нам хорошее сравнение и оттенит различия между данными двумя программами.

Вы можете выгрузить этот код из репозитория Github данной книги, я перечислю здесь все существенные различия:


devices = {'iosv-1': {'ip': '172.16.1.20'}, 'iosv-2': {'ip': '172.16.1.21'}}
 	   

Нам больше нет нужды соответствовать каждому приглашению устройства при применении Paramiko, таким образом наш словарь может быть упрощён:


commands = ['show versionn', 'show runn']
 	   

В Paramiko нет никакого эквивалента отправки строки, поэтому нам приходится вручную включать строчный разрыв в каждой из своих команд:


def clear_buffer(connection):
    if connection.recv_ready():
        return connection.recv(max_buffer)
 	   

Мы включаем новый метод для очистки своего буфера при отправки команд, такой как terminal length 0 или enable так как нам не требуется вывод для этих команд. Мы просто желаем очищать свой буфер и получать приглашение к исполнению. Эта функция позже будет применяться в наших циклах, таких как приведённый в строке 25:


output = clear_buffer(new_connection)
 	   

Оставшаяся часть программыдолжна достаточно пояснять себя самостоятельно, как мы это видели на протяжении данной главы. Последний момент, на который я бы желал обратить внимание, это то, что поскольку данная программа интерактивная, мы размещаем некий буфер и ожидаем пока данная команда будет завершена в нашем удалённом устройстве прежде чем начнём изымать её вывод:


time.sleep(2)
 	   

После того как мы очистили своё буфер, на протяжении промежутка времени между выполнением команд мы осуществляем задержку на две секунды. Это обеспечит нам предоставление для данного устройства соразмерного времени в том случае, если оно занято.

Дополнительная функциональность Paramiko

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

Paramiko для серверов

Paramiko может также применяться для управления серверами через SSHv2. Давайте рассмотрим некий пример того, как мы можем воспользоваться Paramiko для управления серверами. Мы применим аутентификацию на базе ключа для данного сеанса SSHv2.

[Замечание]Замечание

Для данного примера я воспользуюсь другой виртуальной машиной в том же самом гипервизоре в качестве сервера получателя. Вы также можете пименить некий сервер в имитаторе VIRL или некий экземпляр у одного из общедоступных поставщиков облачных служб, например, Amazon AWS EC2.

Мы создадим некую пару общедоступного и частного ключей для своего хоста Paramico:


ssh-keygen -t rsa
 	   

Эта команда по умолчанию создаёт некий общедоступный ключ с названием id_rsa.pub для вашего общедоступного ключа в каталоге вашего пользователя с названием ~/.ssh вместе с неким частным ключом с именем id_rsa. Воспринимайте данный частный ключ как свой пароль, который вы нежелаете выставлять на всеобщее обозрение, а свой общедоступный ключ как некую визитную карточку, которая определяет кто вы такой. Пи их объединении, конкретное сообщение будет зашифровано с вашим частным ключом локально и расшифровано на удалённом хосте при помощи имеющегося общедоступного ключа. Таким образом вам следует скопировать ваш общедоступный ключ на свой удалённый хост. В промышленном применении мы можем делать это вне сетевого обмена с применением USB устройства; в своей лаборатории мы можем просто скопировать такой файл общедоступного ключа в файл ~/.ssh/authorized_keys своего удалённого хоста. Откройте некое окно терминала своего удалённого сервера чтобы вы смогли вставить туда свой общедоступный ключ:

Скопируйте всё содержимое ~/.ssh/id_rsa

на свой подлежащий управлению хост с Paramiko:


<Management Host with Pramiko>$ cat ~/.ssh/id_rsa.pub
ssh-rsa  echou@pythonicNeteng
 	   

Затем поместите его на удалённый хост в требуемом каталоге пользователя; в данном случае и применяю echou ждя обеих сторон:


<Remote Host>$ vim ~/.ssh/authorized_keys
ssh-rsa  echou@pythonicNeteng
 	   

Теперь вы готовы применять Paramiko для управления своим удалённым хостом:


Python 3.5.2 (default, Nov 17 2016, 17:05:23)
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import paramiko
>>> key = paramiko.RSAKey.from_private_key_file('/home/echou/.ssh/id_rsa')
>>> client = paramiko.SSHClient()
>>> client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
>>> client.connect('192.168.199.182', username='echou', pkey=key)
>>> stdin, stdout, stderr = client.exec_command('ls -l')
>>> stdout.read()
b'total 44ndrwxr-xr-x 2 echou echou 4096 Jan 7 10:14 Desktopndrwxr-xr-x 2 echou echou 4096 Jan 7 10:14 Documentsndrwxr-xr-x 2 echou echou 4096 Jan 7 10:14 Downloadsn-rw-r--r-- 1 echou echou 8980 Jan 7 10:03 examples.desktopndrwxr-xr-x 2 echou echou 4096 Jan 7 10:14 Musicndrwxr-xr-x 2 echou echou 4096 Jan 7 10:14 Picturesndrwxr-xr-x 2 echou echou 4096 Jan 7 10:14 Publicndrwxr-xr-x 2 echou echou 4096 Jan 7 10:14 Templatesndrwxr-xr-x 2 echou echou 4096 Jan 7 10:14 Videosn'
>>> stdin, stdout, stderr = client.exec_command('pwd')
>>> stdout.read()
b'/home/echoun'
>>> client.close()
>>>
 	   

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

СОбираем всё воедино для Paramiko

Мы уже почти в конце данной главы. В данном последнем разделе давайте сделаем свою программу Paramiko более доступной к посторному применению. Имеется одна обратная сторона имеющегося у нас сценария; нам требуется открывать данный сценарий всякий раз когда мы желаем добавить или удалить некий хост; или всякий раз, когда нам необходимо обновить имеющиеся у нас команды для их исполнения на удалённом хосте. Помимо наличия повышенного шанса для ошибок, как насчёт того, когда вы позволяете своим сослуживцам или сетевому операционному центру применять свой сценарий? Они могут почувствовать себя не комфортно для работы с Python, Paramiko или Linux.

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

Данный файл имеет название chapter2_4.py.

Мы отсекли все команды в некоторый файл commands.txt. До данного момента нам приходилось применять команды отображения; в данном примере мы внесём изменения настроек для размера своего буфера протокола:


$ cat commands.txt
config t
logging buffered 30000
end
copy run start
 	   

Вся информация об устройствах записывается в некий файл devices.json. Мы выбрали формат JSON для информации о своих устройствах, так как типы данных JSON просто транслируются в типы данных словарей Python:


$ cat devices.json
{
    "iosv-1": {"ip": "172.16.1.20"},
    "iosv-2": {"ip": "172.16.1.21"}
}
 	   

В свой сценарий мы внесём следующие изменения:


with open('devices.json', 'r') as f:
    devices = json.load(f)

with open('commands.txt', 'r') as f:
    commands = [line for line in f.readlines()]
 	   

Вот сокращённый вывод исполнения нашего сценария:


$ python3 chapter2_4.py
Username: cisco
Password:
b'terminal length 0rniosv-2#config trnEnter configuration commands, one per
line. End with CNTL/Z.rniosv-2(config)#'
b'logging buffered 30000rniosv-2(config)#'
...
 	   

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


iosv-1#sh run | i logging
logging buffered 30000
iosv-1#sh start | i logging
logging buffered 30000

iosv-2#sh run | i logging
logging buffered 30000
iosv-2#sh start | i logging
logging buffered 30000
 	   

Заглядывая вперёд

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

Недостатки Pexpect и Paramiko в сравнении с прочими инструментами

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

Мы взглянем на некий лучший способ в своей следующей главе.

Идемпотентное взаимодействие сетевых устройств

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

Так как Pexpect и Paramiko взрывают некие последовательности команд интерактивно, становится более высокой определённая вероятность наличия не идемпотентного взаимодействия. Возвращаясь назад к тому факту, что все возвращаемые результаты подлежат вычленению из экранного вывода на предмет полезных элементов такой риск ещё выше, так как что- то может измениться с того момента как мы написали свой сценарий при исполнении данного сценария на 1000й раз. Например, если производитель внёс изменения в экранный вывод в разные редакции, это может сойти для живого инженера, но это может разрушить ваш сценарий.

Если мы вынуждены полагаться на свой сценарий в промышленном применении, нам необходима идемпотентность нашего сценария настолько, насколько это достижимо.

Плохая автоматизация ускоряет плохие вещи.

Плохая автоматизация позволяет вам чаще попадаться на глаза; это так просто. Компьютеры выполняют задачи намного быстрее живых инженеров. Если бы у нас имелся один и тот же набор рабочих процедур, выполняемых в соревновании между человеком и сценарием, такой сценарий закончил бы дело быстрее чем люди, причём порой без определённого преимущества наличия некоторой твёрдой обратной связи между процедурами. Интернет переполнен историй ужасов о том что кто- то нажал свою клавишу Enter и немедленно пожалел об этом.

Нам необходимо убедиться что наличие вероятности что при плохом сценарии автоматизации что- то может пойти не так мала настолько, насколько это возможно.

Выводы

В данной главе мы охватили все варианты низкого уровня для непосредственного взаимодействия с имеющимися сетевыми устройствами. Без какого- либо способа программируемого взаимодействия и внесения изменений в сетевые устройства не может идти речи об автоматизации. Мы рассмотрели две библиотеки Python, которые позволяют нам управлять имеющимися устройствами которые подразумевают управление через CLI. Помимо пользы, это простой способ увидеть насколько хрупким может быть данный процесс. Это в основном обусловлено тем фактом, что все рассматриваемые сетевые обмены предназначены для управления людьми, а не компьютерами.

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