Глава 3. Работа с сетями через API и движимое намерениями проектирование сетей

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

К счастью интернет сообщество разрешило данную проблему. Представим имеющуюся разницу между некоторыми компьютером и человеком читающими какую- то веб страницу. Данный живой оператор видит слова, картинки и пробелы интерпретируемые его браузером; такой компьютер же видит строки кода HTML, символы Юникода и двоичные файлы. Что происходит когда некий вебсайт должен стать какой- то веб службой для другого компьютера? Не кажется ли вам данная проблема похожей на одну из тех, которые мы представляли ранее? Ответ кроется в прикладном программном интерфейсе, или для краткости API (Application Programming Interface). Согласно Wikipedia понятие API это:

В вычислительном программировании некий API (Прикладной программный интерфейс, Application Programming Interface) является набором определений процедур, протоколов и инструментов для построения прикладных программ. В общем смысле это некий набор чётко определённых методов взаимодействия между различными программными компонентами. Хороший API делает более простой разработку некоторой вычислительной программы предоставляя все необходимые строительные блоки, которые складываются воедино программистом разработчиком.

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

В данной главе мы рассмотрим следующие темы:

  • Трактовку инофраструктуры как кода и модели данных

  • Cisco NX-API и инфраструктура сосредоточенная на приложении

  • Juniper NETCONF и PyEZ

  • Arista eAPI и pyeapi

Инфраструктура как код Python

В идеальном мире сетевые инженеры и люди, которые разрабатывают сетевые среды и управляют ими, должны сосредотачиваться на том чего они хотят достичь от такой сетевой среды вместо того чтобы возиться с взаимодействием на уровне устройства. В моей первой должности в качестве интерна для некоторого локального ISP, когда я воспринимал всё широко открытыми глазами и был взолнован, я получил своё первое задание установить некий маршрутизатор на площадке клиента чтобы включить его секцию соединения ретрансляции кадров (frame relay, помните такое?) Как мне это сделать?, спросил я себя. Я был вооружён некоторой стандартной процедурой для включения соединений ретрансляции кадров. Я поехал на площадку к потребителю, вслепую набрал все команды и посмотрев на мерцающие зелёные светодиоды счастливо упаковал свой багаж и отправился восвояси с чувством хорошо выполненной работы. Я просто следовал инструкциям не размышляя о собственно реализации всех набранных мной команд. Что мне было делать если бы светодиод стал красным вместо зелёного? Я полагаю, мне следовало обратиться назад в свой офис.

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

По моему убеждению, именно применение некоторого API позволит нам приблизиться к некоторому состоянию проектирования сетей, отвечающего определённым целям (intent-driven networking). Коротко говоря, благодаря своему абстрагированию от того уровня. на котором определённая особая команда исполняется на целевом устройстве, мы сосредотачиваемся на своих целеполаганиях вместо того чтобы решать проблему доставки конкретной команды в данное устройство. Например, если наши намерения состоят в запрете некоторого IP для входа в нашу сетевую среду, мы можем применять список доступа группу доступа в каком- то Cisco и список фильтров в некотором Juniper. Однако, при использовании API наша программа может начать запрашивать исполняющую сторону то чего хотят от него достичь и при этом маскировать какой вид физического устройства будет за это отвечать.

Сопоставление анализа терминальных данных и структурированного вывода API

Представим себе некий распространённый сценарий, при котором нам необходимо зарегистрироваться в определённом устройстве и убедиться что все все имеющиеся в этом устройстве интерфейсе в состоянии up/up (и состояние, и протокол отображаются как up). Для нашего живого сетевого инженера, получившего доступ к некоторому устройству Cisco NX-OS, достаточно просто выполнить соответствующую команду show IP interface brief чтобы с лёгкостью сказать по результатам вывода какие интерфейсы подняты:


nx-osv-2# show ip int brief
IP Interface Status for VRF "default"(1)
Interface IP Address Interface Status
Lo0 192.168.0.2 protocol-up/link-up/admin-up
Eth2/1 10.0.0.6 protocol-up/link-up/admin-up
nx-osv-2#
 	   

Переход на новую строку, пробелы и самая первая строка имеющихся колонок заголовка легко различаются человеческим глазом. На самом деле, они имеются здесь чтобы помочь нам выстроить, скажем, все IP адреса всех интерфейсов из строки 1 к строке 2 и 3. Если мы поместим себя на место компьютера, все такие пробелы и переходы на новую строку только уводят прочь от на самом деле важного вывода, а именно: какие интерфейсы пребывают в состоянии up/up? Для иллюстрации данного момента мы можем вновь взглянуть на вывод нашего Paramiko:


>>> new_connection.send('sh ip int briefn')
16
>>> output = new_connection.recv(5000)
>>> print(output)
b'sh ip int briefrrnIP Interface Status for VRF
"default"(1)rnInterface IP Address Interface StatusrnLo0 192.168.0.2 protocol-up/link-up/admin-up rn Eth2/1 10.0.0.6 protocol-up/link-up/admin-up rnrnxosv-2# '
>>>
 	   

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

  1. Отделить каждую строку строчным разрывом.

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

  3. Изъять во второй строке всё вплоть до самого VRF и сохранить его в некоторой переменной, так как мы желаем знать вывод какого именно виртуального маршрутизатора отображается.

  4. Для всех остальных строк, поскольку на самом деле мы не знаем точного числа имеющихся интерфейсов, мы будем выполнять поиск с неким регулярным выражением на предмет того начинается ли данная строка возможным интерфейсом, таким как lo для контура обратной связи и Eth.

  5. Затем мы расщепляем эту строку на три раздела при помощи пробела, причём каждый из них составляют собственно имя данного интерфейса, IP адрес и затем само состояние этого интерфейса.

  6. Само состояние интерфейса затем расщепляется снова при помощи обратного слэша (/), что даёт нам состояния самого протокола, линии и нашего администратора.

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

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

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

  • Привязанность к производителю и программному обеспечению: Возможно самая большая проблема состоит в том, что раз мы тратим такое время на синтаксический разбор всего вывода для данного конкретного производителя и определённую версию программного продукта, в нашем случае Cisco NX-OS, нам потребуется повторить данный процесс для следующего производителя, которого мы выберем. Не знаю как насчёт вас, но если бы мне пришлось оценивать нового производителя, то такой новый производитель будет иметь сразу один серьёзный недостаток на борту если я вынужден буду переписывать весь свой код анализа экранного вывода вновь.

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


{
 "ins_api":{
 "outputs":{
 "output":{
 "body":{
 "TABLE_intf":[
   {
   "ROW_intf":{
   "admin-state":"up",
   "intf-name":"Lo0",
   "iod":84,
   "ip-disabled":"FALSE",
   "link-state":"up",
   "prefix":"192.168.0.2",
   "proto-state":"up"
   }
   },
   {
   "ROW_intf":{
   "admin-state":"up",
   "intf-name":"Eth2/1",
   "iod":36,
   "ip-disabled":"FALSE",
   "link-state":"up",
   "prefix":"10.0.0.6",
   "proto-state":"up"
   }
   }
 ],
 "TABLE_vrf":[
   {
   "ROW_vrf":{
   "vrf-name-out":"default"
   }
   },
   {
   "ROW_vrf":{
   "vrf-name-out":"default"
   }
   }
 ]
 },
 "code":"200",
 "input":"show ip int brief",
 "msg":"Success"
 }
 },
 "sid":"eoc",
 "type":"cli_show",
 "version":"1.2"
 }
 }
 	   

NX-API может возвращать результаты в XML или JSON, и это именно тот вывод JSON, который мы искали. Прямо сейчас вы можете видеть, что все ответы структурированы и могут напрямую соответствовать имеющейся в Python структуре данных словаря. Не требуется никакого синтаксического разбора, поэтому просто указывайте тот ключ, который желаете и выбираете то значение, которое связано с этим ключом. Имеется также некое дополнительное преимущество в некотором коде для указания успешности или отказа команды, при наличии сообщения, говорящего все относящиеся к успеху или отказу основания отправителя. Вам больше не придётся сохранять отслеживание исполнения данной команды, так как она уже возвращена в имеющемся поле input. Также присутствуют некие метаданные, например, текущая версия NX-API.

Такой тип вывода делает более простой жизнь как для производителя, так и для операторов. С точки зрения производителя, они могут более простым способом передавать информацию о настройках и состоянии и выстраивать свою инфраструктуру вокруг этого. Обычно не вызывает сомнений то, что автоматизация во многом необходима и является полезной вещью. Как вы можете обнаружить позже в данной главе , под зонтиком API пребывает множество соперничающих технологий, что касается только самой транспортной стороны дела, у нас помимо всего прочего имеются REST API, NETCONF и RESTCONF. В конечном счёте всё определит всеобъемлющий рынок, но в то же время мы должны вернуться на шаг обратно и решить какая именно технология наилучшим образом отвечает нашим потребностям.

Моделирование данных для инфраструктуры как код

Согласно Wukipedia:

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

Процесс моделирования данных может быть представлен следующей диаграммой:

 

Рисунок 1


Процесс моделирования данных (источник)

Что касается сетевых сред, мы можем применить данную концепцию как некую абстрактную модель, которая определяет нашу сеть, будь то ЦОД, кампус или глобальная Всемирная сеть связи (WAN, Wide Area Network). Если мы пристальнее рассмотрим некий физический ЦОД, некий коммутатор Ethernet 2 уровня может представляться как некий прибор, содержащий какую- то таблицу Mac адресов, соответствующих каждому порту. Наша модель коммутации описывает как эти Mac адреса должны храниться в таблице, которая содержит все ключи, дополнительные характеристики (представьте себе VLAN и частную VLAN), и тому подобное. Аналогично мы можем выйти за пределы устройств и представить в виде некоторой модели свой ЦОД. Мы можем начать с общего числа устройств на каждом из уровней доступа, распределения, ядра, как они взаимосвязаны и как должны вести себя в некоторой промышленной среде. Например, если у нас имеется сеть FatTree, сколько связей должен иметь каждый из имеющихся маршрутизаторов ствола, сколько маршрутизаторов он должен содержать и сколько последующих прыжков (hop) имеет каждый из определённых префиксов. Такие характеристики могут быть поставлены в соответствие в некотором формате, на который можно ссылаться как на то идеальное состояние, которое нам следует всегда проверять.

Одним из относительно новых языков моделирования сетевых данных, который набирает силу, является YANG. Yet Another Next Generation (YANG, Ещё одно новое поколение, несмотря на распространённое мнение, некоторые из рабочих групп IETF не лишены чувства юмора). Он впервые был опубликован в RFC 6020 в 2010 и получил поддержку со стороны производителей и операторов. На момент написания данной книги общая поддержка YANG сильно варьируется в зависимости от производителей платформ. Имеющаяся скорость адаптации в промышленности, таким образом, относительно низкая. Однако это именно та технология, за которой стоит стоит следить одним глазом.

API и ACI Cisco

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

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

  • Автоматизацию продуктов Nexus с помощью NX-API

  • Cisco NETCONF и примеры YANG

  • Сосредоточенную на приложениях инфраструктуру Cisco для ЦОД

  • Сосредоточенную на приложениях инфраструктуру Cisco для корпораций

Для представленных здесь примеров NX-API и NETCONF, мы можем применять либо всегда доступные через Интернет лабораторные устройства Cisco DevNet, либо локально исполнять Cisco VIRL. Так как ACI является неким отдельным продуктом и лицензируется поверх физического коммутатора, для всех последующих примеров ACI я рекомендую применять доступные лабораторные DevNet для получения представления об этих инструментах, если, конечно, вы не один из тех счастливчиков, которые имеют некую частную ACI лабораторию которой вы можете воспользоваться.

Cisco NX-API

Nexus является продуктовой линейкой коммутаторов ЦОД Cisco. NXAPI позволяет работающему с ним инженеру взаимодействовать с данным коммутатором извне данного устройства с применением различных транспортных протоколов, включающих в себя SSH, HTTP и HTTPS.

Установка лабораторного ПО и подготовка устройства

Вот те пакеты Ubuntu, которые мы установим, причём вы уже можете иметь некоторые из этих пакетов, такие как разработка Pthon, pip и Git:


$ sudo apt-get install -y python3-dev libxml2-dev libxslt1-dev libffi-dev libssl-dev zlib1g-dev python3-pip git python3-requests
 	   
[Замечание]Замечание

Если вы применяете Python 2, воспользуйтесь вместо приведённой команды такой строкой:


sudo apt-get install -y python-dev libxml2-dev libxslt1-dev libffi-dev libssl-dev zlib1g-dev python-pip git python-requests
 	   

Библиотека ncclient является библиотекой Python для клиентов NETCONF, поэтому мы установим её из репозитория GitHub чтобы установить самую последнюю версию:


$ git clone https://github.com/ncclient/ncclient
$ cd ncclient/
$ sudo python3 setup.py install
$ sudo python setup.py install
 	   

По умолчанию NX-API отключён в устройствах Nexus, поэтому нам необходимо его включить. Мы можем либо применить того пользователя, который уже создан, либо создать некоего нового пользователя для всех процедур NETCONF:


feature nxapi
username cisco password 5 $1$Nk7ZkwH0$fyiRmMMfIheqE3BqvcL0C1 role network-operator
username cisco role network-admin
username cisco passphrase lifetime 99999 warntime 14 gracetime 3
 	   

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


nx-osv-2(config)# nxapi http port 80
nx-osv-2(config)# nxapi sandbox
 	   

Теперь мы готовы ознакомиться с нашим первым примером NX-API.

Примеры NX-API

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

 

Рисунок 2



В своём следующем примере я выбрал JSON-RPC и тип команд CLI для вызова команды show version:

 

Рисунок 3



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

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


#!/usr/bin/env python3
from ncclient import manager
conn = manager.connect(
        host='172.16.1.90',
        port=22,
        username='cisco',
        password='cisco',
        hostkey_verify=False,
        device_params={'name': 'nexus'},
        look_for_keys=False)
for value in conn.server_capabilities:
    print(value)
conn.close_session()
 	   

Все параметры соединения достаточно объясняют себя сами: хост, порт, имя пользователя и пароль. Параметр самого устройства определяет тот вид устройства, к которому подключается данный клиент. Мы также увидим некую дифференциацию в разделе NETCONF Juniper. hostkey_verify bypass пробрасывает соответствующие known_host требующиеся для SSH, в противном все хосты необходимо перечислить в файле ~/.ssh/known_hosts. Параметр look_for_keys отключает аутентификацию по общедоступному ключу и применяет для аутентификации имя пользователя и пароль.

Полученный вывод отобразит тот факт, что XML и NETCONF являются поддерживаемыми свойствами для данной версии NX-OS:


$ python3 cisco_nxapi_1.py
urn:ietf:params:xml:ns:netconf:base:1.0
urn:ietf:params:netconf:base:1.0
 	   

Применение ncclient и NETCONF поверх SSH это великолепно, поскольку приближает нас к естественным реализации и синтаксису. Мы будем применять эту библиотеку много раз в дальнейшем. Что касается NX-API, лично я полагаю, что проще иметь дело с HTTPS и JSON-RPC. На нашем предыдущем экранном снимке NX-API Developer Sandbox, если вы сделаете пометку в блоке Request, будет иметься некий блок, помеченный как Python. Если вы кликните по нему, у вас появится возможность получать автоматически преобразуемый сценарий Python на основе библиотеки requests.

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

Requests является очень популярной, самопровозглашённой HTTP библиотекой для самого Человека, используемая такими компаниями как Amazon, Google, NSA и многими другими. Вы можете обнаружить дополнительную информацию по ней на официальном сайте http://docs.python-requests.org/en/master/.

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


"""
NX-API-BOT
"""
import requests
import json

"""
Modify these please
"""
url='http://YOURIP/ins'
switchuser='USERID'
switchpassword='PASSWORD'

myheaders={'content-type':'application/json-rpc'}
payload=[
  {
    "jsonrpc": "2.0",
    "method": "cli",
    "params": {
      "cmd": "show version",
      "version": 1.2
    },
    "id": 1
  }
]
response = requests.post(url,data=json.dumps(payload),
headers=myheaders,auth=(switchuser,switchpassword)).json()
 	   

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


$ python3 cisco_nxapi_2.py
7.2(0)D1(1) [build 7.2(0)ZD(0.120)]
 	   

Самая лучшая часть данного метода стотоит в том, что тот же самый синтаксис работает как с командами настройки, так и с командами отображения. Это проиллюстрировано в нашем файле cisco_nxapi_3.py. для настроек со множеством строк вы можете применять имеющееся поле id для определения необходимого порядка действий. В файле cisco_nxapi_4.py приводимая ниже полезная нагрузка перечислена для изменения имеющегося определения нашего интерфейса Ethernet 2/12 в режиме настройки данного интерфейса:


[
  {
    "jsonrpc": "2.0",
    "method": "cli",
    "params": {
      "cmd": "interface ethernet 2/12",
      "version": 1.2
    },
    "id": 1
  },
  {
    "jsonrpc": "2.0",
    "method": "cli",
    "params": {
      "cmd": "description foo-bar",
      "version": 1.2
    },
    "id": 2
  },
  {
    "jsonrpc": "2.0",
    "method": "cli",
    "params": {
      "cmd": "end",
      "version": 1.2
    },
    "id": 3
  },
  {
    "jsonrpc": "2.0",
    "method": "cli",
    "params": {
      "cmd": "copy run start",
      "version": 1.2
    },
    "id": 4
  }
]
 	   

В нашем следующем разделе мы рассмотрим соответствующие примеры для Cisco NETCONF и модели YANG.

Cisco и модель YANG

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

Прежде всего, мы должны уяснить, что YANG определяет только те типы данных, которые отправляются поверх протокола NETCONF, причём NETCONF присутствует как некий отдельно стоящий протокол, как мы уже видели в разделе с NX-API. YANG, будучи относительным новичком, имеет пятнистую поддержку по производителям и продуктовым линейкам. Например, если мы исполним тот же самый сценарий обмена возможностями, что мы применяли ранее, к Cisco 1000v под управлением IOS-XE, мы обнаружим следующее:


urn:cisco:params:xml:ns:yang:cisco-virtual-service?module=cisco-virtual-service&revision=2015-04-09
http://tail-f.com/ns/mibs/SNMP-NOTIFICATION-MIB/200210140000Z?module=SNMP-NOTIFICATION-MIB&revision=2002-10-14
urn:ietf:params:xml:ns:yang:iana-crypt-hash?module=iana-crypthash&revision=2014-04-04&features=crypt-hash-sha-512,crypt-hashsha-256,crypt-hash-md5
urn:ietf:params:xml:ns:yang:smiv2:TUNNEL-MIB?module=TUNNELMIB&revision=2005-05-16
urn:ietf:params:xml:ns:yang:smiv2:CISCO-IP-URPF-MIB?module=CISCOIP-URPF-MIB&revision=2011-12-29
urn:ietf:params:xml:ns:yang:smiv2:ENTITY-STATE-MIB?module=ENTITYSTATE-MIB&revision=2005-11-22
urn:ietf:params:xml:ns:yang:smiv2:IANAifType-MIB?module=IANAifTypeMIB&revision=2006-03-31
<пропущено>
 	   

Сравним данный вывод с тем что мы уже видели; ясно что IOS-XE понимает модель YANG лучше чем NX-OS. Широкое моделирование сетевых данных в отрасли очевидно является чем- то что привносит преимущества в сетевую автоматизацию. Однако представленная неравномерная поддержка по производителям и продуктам не является чем- то тем, что является достаточно зрелым для применения по всем промышленным сетевым средам, по крайней мере с моей точки зрения. Для данной книги я включил некий сценарий с названием cisco_yang_1.py , который показывает как выполнять синтаксический разбор вывода NETCONF XML с применением фильтров YANG имеющих название urn:ietf:params:xml:ns:yang:ietf-interfaces в качестве некоторой отправной точки для просмотра имеющегося тега возможнстей.

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

Вы можете проверить все самые последние поддержки производителей на странице проекта YANG GitHub.

Cisco ACI

Cisco ACI (Application Centric Infrastructure, Ориентированная на приложение инфраструктура) предназначена для предоставления некоторого централизованного подхода ко всем имеющимся сетевым компонентам. В контексте ЦОД это означает что имеющийся центральный контроллер осведомлён и управляет всеми коммутаторами ствола, листьев, TOR (top of rack), а также всеми имеющимися функциями сетевых служб. Это может выполняться через GUI, CLI или API. Имеется аргументация что данный ACI Cisco отвечает имеющемуся обширному программно- определяемому построению сетей.

Одним их сбивающих с толку моментов в ACI является имеющаяся разница между ACI и ACI-EM. Если кратко, ACI сосредоточена на операциях ЦОД, в то время как ACI-EM в основном фокусируется на корпоративных модулях. Оба предлагают некий централизованный просмотр и управление всеми сетевыми компонентами, но каждый имеет свои собственные средоточия и совместные инструменты. Например, достаточно часто можно обнаружить как любой ЦОД равёртывает предназначенную для пользователей инфраструктуру беспроводной связи, однако беспроводные сети сегодня являются критически важной частью корпораций сегодня. Другим примером были бы имеющиеся различия в подходах к сетевой безопасности. Хотя безопасность важна в любой сетевой среде, в имеющейся среде ЦОД большая часть политик безопасности вытесняется на краевые узлы сервера для целей масштабируемости; в корпоративной безопасности политики являются чем- то, что совместно используется всеми сетевыми устройствами и серверами.

В отличие от NETCONF RPC, ACI API следует модели REST для применения глаголов HTTP (GET, POST, PUT, DELETE) чтобы определять намеченные операции.

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

Мы можем взглянуть на свой файл cisco_apic_em_1.py, который является некоторой модификацией версии кода примера Cisco lab2-1-get-network-devicelist.py.

Здесь перечислены сокращённые разделы без комментариев и пробелов.

Самая первая функция с названием getTicket() применяет HTTPS POST в своём контроллере с вызываемым путём /api/vi/ticket и именем пользователя и паролем встроенными в сам заголовок. Затем синтаксический разбор возвращает отклик для какого- то сертификата (ticket) с некоторым ограничением по времени:


def getTicket():
    url = "https://" + controller + "/api/v1/ticket"
    payload = {"username":"usernae","password":"password"}
    header = {"content-type": "application/json"}
    response= requests.post(url,data=json.dumps(payload), headers=header, verify=False)
    r_json=response.json()
    ticket = r_json["response"]["serviceTicket"]
    return ticket
 	   

Наша вторая функция затем вызывает другой путь называемый /api/v1/network-devices с более новым полученным сертификатом (ticker), встроенным в сам заголовок, а затем делает синтаксический разбор полученных результатов:


url = "https://" + controller + "/api/v1/network-device"
header = {"content-type": "application/json", "X-Auth-Token":ticket}
 	   

Получаемый вывод отображает и сырой вывод отклика JSON и некую таблицу синтаксического разбора. Здесь приводится частичный вывод при исполнении для лабораторного контроллера DevNet:


Network Devices =
{
 "version": "1.0",
 "response": [
 {"
 reachabilityStatus": "Unreachable",
 "id": "8dbd8068-1091-4cde-8cf5-d1b58dc5c9c7",
 "platformId": "WS-C2960C-8PC-L",
<пропуск>
 "lineCardId": null,
 "family": "Wireless Controller",
 "interfaceCount": "12",
 "upTime": "497 days, 2:27:52.95"
 }
]
}
8dbd8068-1091-4cde-8cf5-d1b58dc5c9c7 Cisco Catalyst 2960-C Series
 Switches
cd6d9b24-839b-4d58-adfe-3fdf781e1782 Cisco 3500I Series Unified
Access Points
<пропуск>
55450140-de19-47b5-ae80-bfd741b23fd9 Cisco 4400 Series Integrated
Services Routers
ae19cd21-1b26-4f58-8ccd-d265deabb6c3 Cisco 5500 Series Wireless LAN
Controllers
 	   

Как мы можем увидеть, мы опросили некое отдельное устройство контроллера, однако мы способны получить некий обзор на верхнем уровне всех имеющихся сетевых устройств, о которых осведомлён данный контроллер. Обратной стороной, конечно, является то, что такой контроллер ACI поддерживает только устройства Cisco на данный момент.

API Python для сетевых сред Juniper

Сетевое оборудование Juniper всегда было предпочитаемо фанами среди толп поставщиков услуг. Если мы отступим на шаг назад и взглянем на имеющуюся вертикаль поставщиков услуг, можно ощутить, что оборудование с автоматизацией сетевой среды всегда находится в верхних строчках их сознания. Задолго до восхода ЦОД облачного масштаба поставщики услуг были одними из тех, кто имел наилучшее сетевой оборудование. Типичная корпорация могла иметь несколько резервируемых Интернет соединений в своей корпоративной штабквартире, а также иметь несколько удалённых площадок для размещения всех сетевых соединений обратно со своими головными офисами неким образом хаба и спиц для осуществления доступа к ресурсам штабквартиры, таким как почта и базы данных. Однако, что касается поставщиков услуг, они были именно теми, кому было необходимо строить, предоставлять, управлять и искать неисправности для подобных соединений и всех лежащих в основе сетевых сред. Они делали свои деньги на продаже имеющейся полосы пропускания помимо обладающих дополнительными возможностями управляемых служб. Для таких поставщиков сетевых служб имело смысл вкладываться в автоматизацию, чтобы использовать как можно меньший объём инженерных часов для жужжания сети, а автоматизация является ключом к этому.

По моему мнению, основное отличие между потребностями каких- то поставщиков сетевых услуг при сопоставлении с их аналогами для облачных ЦОД заключалось с том, что обычно поставщики услуг соединяют всё в некое отделное устройство с добавленными службами. Хорошим примером может послужить MPLS (Multiprotocol Label Switching, Многопротокольная коммутация по меткам), которую предоставляют почти все сетевые поставщики, однако она редко применяется в сетевых средех корпораций и ЦОД. Juniper, поскольку они были очень успешными, определили эту потребность и преуспели в выполнении требований поставщика услуг в вопросах автоматизации. Давайте взглянем на некоторые API автоматизации Juniper.

Juniper и NETCONF

Протокол NETCONF (Network Configuration Protocol, Протокол сетевой настройки) является неким стандартом IETF, который впервые был опубликован в 2006 как RFC 4741, который позже был пересмотрен как RFC 6241. В оба этих RFC Juniper внёс большой вклад; фактически в RFC 4741 Juniper был единственным автором. Это имело смысл, так как устройства Juniper полностью поддерживают NETCONF, а он служит базовым уровнем для большей части их средств и инфраструктур автоматизации. Некоторые из основных свойств NETCONF включают в себя:

  1. Для кодирования данных ин применяет XML (Extensible Markup Language, Расширяемого языка разметки).

  2. Он применяет RPC (Remote Procedure Calls, Удалённый вызов процедур), поэтому в случае применения HTTP(s) в качестве транспортного протокола, имеющаяся конечная точка URL всегда идентична, в то время как сама применяемая операция указывается в самом теле данного запроса.

  3. Он концептуально построен на уровнях; с самого верха донизу, причём они включают само содержимое, операции, сообщения и транспорт:

     

    Рисунок 4


    Модель NetConf (источник)

Juniper networks предоставляет громадное руководство разработчика протокола управления NETCONF XML в своей технической библиотеке. Давайте бросим взгляд на его применение.

Подготовка устройства

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


set system login user netconf uid 2001
set system login user netconf class super-user
set system login user netconf authentication encrypted-password "$1$0EkA.XVf$cm80A0GC2dgSWJIYWv7Pt1"
set system services ssh
set system services telnet
set system services netconf ssh port 830
 	   
[Замечание]Замечание

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

На данном устройстве Juniper вы всегда можете взглянуть на все настройки или в виде некоторого обычного текстового файла, или в формате XML. Имеющийся обычный текстовый файл удобен когда вам необходимо определить команду в одну строку для внесения изменений в настройку:


netconf@foo> show configuration | display set
set version 12.1R1.9
set system host-name foo
set system domain-name bar
<пропуск>
 	   

Формат XML становится удобным, огда вам нужно просмотреть всю структуру XML вашей нстройки:


netconf@foo> show configuration | display xml
<rpc-reply xmlns:junos="http://xml.juniper.net/junos/12.1R1/junos">
 <configuration junos:commit-seconds="1485561328" junos:commitlocaltime="2017-01-27 23:55:28 UTC" junos:commit-user="netconf">
 <version>12.1R1.9</version>
 <system>
 <host-name>foo</host-name>
<domain-name>bar</domain-name>
 	   
[Замечание]Замечание

Мы установили все необходимые библиотеки Linux и нужную нам библиотеку Python ncclient в своём разделе Cisco. Если вы не сделали этого, вернитесь обратно в этот раздел и установите все необходимые пакеты.

Теперь мы готовы рассмотреть свой первый пример Juniper NETCONF.

Примеры Juniper NETCONF

Мы воспользуемся достаточно прямолинейным примером для выполнения show version. Мы назовём данный файл junos_netconf_1.py:


#!/usr/bin/env python3

from ncclient import manager

conn = manager.connect(
    host='192.168.24.252',
    port='830',
    username='netconf',
    password='juniper!',
    timeout=10,
    device_params={'name':'junos'},
    hostkey_verify=False)

result = conn.command('show version', format='text')
print(result)
conn.close_session()
 	   

Все поля в данном примере в достаточной степени объясняют себя сами, за исключением device_params. Начиная с ncclient 0.4.1, имеющийся для определения различных производителей и платформ был добавлен определённый обработчик устройства, например, его названием может быть juniper, CSR, Nexus или Huawei. Мы также добавили hostkey_verify=False, так как мы применяем в своём устройстве Juniper самоподписываемый сертификат.

Весь возвращаемый вывод является неким rpc-reply, закодированном в XML при помощи элемента output:


<rpc-reply message-id="urn:uuid:7d9280eb-1384-45fe-be48-b7cd14ccf2b7">
 <output>
 Hostname: foo
 Model: olive
 JUNOS Base OS boot [12.1R1.9]
 JUNOS Base OS Software Suite [12.1R1.9]
 <пропуск>
 JUNOS Runtime Software Suite [12.1R1.9]
 JUNOS Routing Software Suite [12.1R1.9]
 </output>
</rpc-reply>
 	   

Мы можем выполнить синтаксический разбор всоего вывода XML чтобы просто включить текст вывода:


print(result.xpath('output')[0].text)
 	   

В junos_netconf_2.py мы внесём изменения в настройку данного устройства. Мы начнём с некоторого нового импорта для построения новых элементов XML и соединения с диспетчером объекта:


#!/usr/bin/env python3

from ncclient import manager
from ncclient.xml_ import new_ele, sub_ele

conn = manager.connect(host='192.168.24.252', port='830', username='netconf' , password='juniper!', timeout=10, device_params={'name':'junos'}, hostkey_v erify=False)
 	   

Мы заблокируем данную настройку и внесём изменения в конфигурацию:


# блокировка настройки и внесение изменений в конфигурацию
conn.lock()

# построение конфигурации
config = new_ele('system')
sub_ele(config, 'host-name').text = 'master'
sub_ele(config, 'domain-name').text = 'python'
 	   

Из отображаемого XML вы можете увидеть что вся структура данного узла с 'system' является определённым родителем для 'host-name' и 'domain-name':


<system>
   <host-name>foo</host-name>
   <domain-name>bar</domain-name>
...
</system>
 	   

Затем мы можем выполнит активную доставку данной конфигурации и фиксацию настройки. Это обычные этапы практики применения изменений настроек Juniper:


# отправка, проверка и фиксация настроек
conn.load_configuration(config=config)
conn.validate()
commit_config = conn.commit()
print(commit_config.tostring)

# разблокировка настроек
conn.unlock()

# закрытие сеанса
conn.close_session()
 	   

В целом, все этапы NETCONF достаточно хорошо соответствуют всему, что мы делали на этапах своего CLI. Взгляните, пожалуйста, на наш сценарий junos_netconf_3.py, который комбинирует все наши предыдущие примеры и пример работы.

Juniper PyEZ для разработчиков

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

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

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

Установка и подготовка

Все необходимые инструкции по установке для всех операционных систем можно найти на странице Installing Junos PyEZ. Далее мы отобразим необходимые инструкции по установке для Ubuntu 16.04.

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


$ sudo apt-get install -y python3-pip python3-dev libxml2-dev libxslt1-dev libssl-dev libffi-dev
 	   

Пактеы PyEZ packages могут быть установлены при помощи pip. Здесь я выполняю установку и для Python 3, и для Python 2:


$ sudo pip3 install junos-eznc
$ sudo pip install junos-eznc
 	   

На самом устройстве NETCONF должен быть настроен как предоставляющий XML API для PyEZ:


set system services netconf ssh port 830
 	   

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


set system login user netconf uid 2001
set system login user netconf class super-user
set system login user netconf authentication encrypted-password
"$1$0EkA.XVf$cm80A0GC2dgSWJIYWv7Pt1"
 	   

Для аутентификации по ключам ssh вначале создадим пару ключей:


$ ssh-keygen -t rsa
 	   

По умолчанию наш общедоступный ключ будет иметь название id_rsa.pub и располагаться в ~/.ssh/, в то время как частный ключ именуется id_rsa в том же самом каталоге. Рассматривайте частный ключ в качестве пароля, который вы никогда не желаете совместно использовать. Ваш общедоступный ключ может свободно распространяться; однако, в данном случае мы переместим его в свой каталог /tmp и разрешим модулю сервера HTTP Python 3 создать некий достижимый URL:


$ mv ~/.ssh/id_rsa.pub /tmp
$ cd /tmp
$ python3 -m http.server
Serving HTTP on 0.0.0.0 port 8000 ...
 	   
[Замечание]Замечание

Для Python 2 вместо этого воспользуйтесь python -m SimpleHTTPServer.

На текущий момент мы можем создать нужного пользователя и связанный с ним общедоступный ключ:


netconf@foo# set system login user echou class super-user authentication load-key-file http://192.168.24.164:8000/id_rsa.pub
/var/home/netconf/...transferring.file........100% of 394 B 2482 kBps
 	   

Теперь, если мы попытаемся установить ssh при помощи своего частного ключа, с данной станции управления, такой пользователь будет автоматически аутентифицирован:


$ ssh -i ~/.ssh/id_rsa 192.168.24.252
--- JUNOS 12.1R1.9 built 2012-03-24 12:52:33 UTC
echou@foo>
 	   

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


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.
>>> from jnpr.junos import Device
>>> dev = Device(host='192.168.24.252', user='netconf', password='juniper!')
>>> dev.open()
Device(192.168.24.252)
>>> dev.facts
{'serialnumber': '', 'personality': 'UNKNOWN', 'model': 'olive', 'ifd_style': 'CLASSIC', '2RE': False, 'HOME': '/var/home/juniper', 'version_info': junos.version_info(major=(12, 1), type=R, minor=1, build=9), 'switch_style': 'NONE', 'fqdn': 'foo.bar', 'hostname': 'foo', 'version': '12.1R1.9', 'domain': 'bar', 'vc_capable': False}
>>> dev.close()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.
>>> from jnpr.junos import Device
>>> dev = Device(host='192.168.24.252', user='netconf', password='juniper!')
>>> dev.open()
Device(192.168.24.252)
>>> dev.facts
{'serialnumber': '', 'personality': 'UNKNOWN', 'model': 'olive', 'ifd_style': 'CLASSIC', '2RE': False, 'HOME': '/var/home/juniper', 'version_info': junos.version_info(major=(12, 1), type=R, minor=1, build=9), 'switch_style': 'NONE', 'fqdn': 'foo.bar', 'hostname': 'foo', 'version': '12.1R1.9', 'domain': 'bar', 'vc_capable': False}
>>> dev.close()
 	   

Мы также можем воспользоваться аутентификацией по ключу ssh:


>>> from jnpr.junos import Device
>>> dev1 = Device(host='192.168.24.252', user='echou',
ssh_private_key_file='/home/echou/.ssh/id_rsa')
>>> dev1.open()
Device(192.168.24.252)
>>> dev1.facts
{'HOME': '/var/home/echou', 'model': 'olive', 'hostname': 'foo', 'switch_style': 'NONE', 'personality': 'UNKNOWN', '2RE': False, 'domain': 'bar', 'vc_capable': False, 'version': '12.1R1.9', 'serialnumber': '', 'fqdn': 'foo.bar', 'ifd_style': 'CLASSIC', 'version_info': junos.version_info(major=(12, 1), type=R, minor=1, build=9)}
>>> dev1.close()
 	   

Великолепно! Иеперь мы готовы посмотреть на некоторые примеры для PyEZ.

Примеры PyEZ

В своём предыдущем интерактивном приглашении на ввод мы уже видели что когда данное устройство подключается, сам объект уже автоматически извлекает некоторые факты об этом устройстве. В нашем первом примере, junos_pyez_1.py мы подключались к своему устройству и выполняли вызов RPC для show interface em1:


#!/usr/bin/env python3
from jnpr.junos import Device
import xml.etree.ElementTree as ET
import pprint

dev = Device(host='192.168.24.252', user='juniper', passwd='juniper!')

try:
    dev.open()
except Exception as err:
    print(err)
    sys.exit(1)

result = dev.rpc.get_interface_information(interface_name='em1', terse=True)
pprint.pprint(ET.tostring(result))

dev.close()
 	   

Наш класс устройства имеет некоторое надлежащее свойство rpc, которое содержит все исполняемые команды. Это несколько настораживает, так как нет склейки между тем что мы можем делать в CLI с API. Хитрость в том, что мы должны найти теговый элемент xml rpc. В нашем первом примере как мы узнали что show interface em1 эквивалентно get_interface_information? У нас есть три способа нахождения информации об этом:

  1. Мы можем обратиться к Junos XML API Operational Developer Reference.

  2. Мы также можем воспользоваться CLI и отобразить все эквиваленты XML RPC с заменой тире - между всеми словами на подчёркивание _.

  3. Мы можем запрограммировать это при помощи имеющейся библиотеки PyEZ.

Обычно я использую второй вариант для непосредственного получения результата:


netconf@foo> show interfaces em1 | display xml rpc
<rpc-reply xmlns:junos="http://xml.juniper.net/junos/12.1R1/junos">
 <rpc>
 <get-interface-information>
 <interface-name>em1</interface-name>
 </get-interface-information>
 </rpc>
 <cli>
 <banner></banner>
 </cli>
</rpc-reply>
 	   

Однако третий вариант также осуществим:


>>> dev1.display_xml_rpc('show interfaces em1', format='text')
'<get-interface-information>n <interface-name>em1</interfacename>n</get-interface-information>n'
 	   

Конечно, нам необходимо выполнить также изменения настроек. В своём примере junos_pyez_2.py мы импортируем некий дополнительный метод Config() из PyEZ:


#!/usr/bin/env python3
from jnpr.junos import Device
from jnpr.junos.utils.config import Config
 	   

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


dev = Device(host='192.168.24.252', user='juniper', passwd='juniper!')

try:
    dev.open()
except Exception as err:
    print(err)
    sys.exit(1)
 	   

Наш новый метод Config() загрузит все данные XML и внесёт все необходимые изменения настроек:


config_change = """
<system>
  <host-name>master</host-name>
  <domain-name>python</domain-name>
</system>
"""

cu = Config(dev)
cu.lock()
cu.load(config_change)
cu.commit()
cu.unlock()

dev.close()
 	   

Все примеры PyEZ просты в построении, но, надеюсь, демонстрируют все способы которыми вы можете усилить свои потребности автоматизации Junos с помощью PyEZ.

API Python Arista

Arista Networks всегда была сосредоточена на сетевых средах ЦОД крупного масштаба. На своей странице корпоративного профиля они постулируют следующее:

"Arista Networks была основана чтобы быть первооткрывателем и поставщиком движимых программным обеспечением облачных сетевых решений для крупный ЦОД сред хранения и вычисления."

Обратите внимание, что данный постулат намеренно называет наш крупный ЦОД, который, как мы уже знаем, подорван серверами, базами данных и, да, сетевым оборудованием тоже. Таким образом в их сознании всегда присутствует автоматизация. На самом деле они имеют в фундаменте Linux в качестве своей операционной системы, затем EOS имеет много дополнительных преимуществ, таких как наличие уже встроенного Python с API Linux.

История автоматизации Arista состоит из трёх подходов:

 

Рисунок 5


Автоматизация EOS (источник)

Как и в случае с их сетевыми конкурентами, вы можете взаимодействовать с устройствами Arcsta напрямую через eAPI или можете выбрать с целью усиления их библиотеку Python для осуществления взаимодействия. Мы рассмотрим примеры обоих вариантов. В следующих главах мы взглянем на интеграцию с Arista для Ansible.

Управление eAPI Arista

eAPI Arista впервые был представлен в EOS 4.12 несколько лет назад. Он выполняет транспорт некоторого перечня команд отображения или настройки через HTTP или HTTPS и предоставляет ответы в виде JSON. Некоторое существенное отличие состоит в том, что он является RPC (Remote Procedure Call, Вызовом удалённых процедур) и JSON-RPC, вместо предоставления передачи состояния и REST, даже хотя самим транспортным уровнем является HTTP(S). Для наших намерений и целей основная разница в том, что мы делаем сам запрос к одному и тому же URL оконечной точки, применяя один и тот же метод HTTP (POST). Вместо применения глаголов HTTP (GET, POST, PUT, DELETE) для выражения нашего действия, мы просто устанавливаем желаемое нами действие в теле своего запроса. В случае с eAPI мы будем определять ключ method с некоторым значением runCmds для своих намерений.

Для всех последующих примеров я применяю некий физический коммутатор Arista под управлением EOS 4.16.

Подготовка eAPI

Имеющийся в устройстве Arista агент eAPI по умолчанию отключён, поэтому нам необходимо разрешить его в своём устройстве прежде чем мы сможем им воспользоваться:


arista1(config)#management api http-commands
arista1(config-mgmt-api-http-cmds)#no shut
arista1(config-mgmt-api-http-cmds)#protocol https port 443
arista1(config-mgmt-api-http-cmds)#no protocol http
arista1(config-mgmt-api-http-cmds)#vrf management
 	   

Вы можете видеть, что мы отключили сервер HTTP и применяем вместо этого для транспорта HTTPS. Начиная с нескольких более ранних версий EOS сам интерфейс управления по умолчанию, располагающийся в VRF имеет название management (Управление). В моей топологии я осуществляю доступ к своему устройству через имеющийся интерфейс Управления; таким образом я особым образом определяю свою VRF (виртуальную маршрутизацию и защиту межсетевым экраном). При помощи соответствующей команды отображения вы можете убедиться что управление API включено:


arista1#sh management api http-commands
Enabled: Yes
HTTPS server: running, set to use port 443
HTTP server: shutdown, set to use port 80
Local HTTP server: shutdown, no authentication, set to use port 8080
Unix Socket server: shutdown, no authentication
VRF: management
Hits: 64
Last hit: 33 seconds ago
Bytes in: 8250
Bytes out: 29862
Requests: 23
Commands: 42
Duration: 7.086 seconds
SSL Profile: none
QoS DSCP: 0
 User Requests Bytes in Bytes out Last hit
----------- -------------- -------------- --------------- --------------
 admin 23 8250 29862 33 seconds ago

URLs
-----------------------------------------
Management1 : https://192.168.199.158:443

arista1#
 	   

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

 

Рисунок 6


Проводник Arista EOS

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

 

Рисунок 7


Просмотр проводника Arista EOS

Обозреваемая ссылка предоставит вам определённый пример использования и лежащую в основе информацию, в то время как вся документация по данной команде будет предоставлена в виде точки ссылки для возможного отображения команд. Каждая из эталонных команд будет содержать имя поля возвращаемого значения, тип и краткое описание. Ссылка на доступный через Интернет сценарий от Arista применяет jsonrpclib, который мы также применяем. Однако, что касается данной книги, он имеет зависимости Python 2.6+ и пока не портирован в Python 3; таким образом, для своих примеров мы применяем Python 2.7.

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

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

Установка прямолинейна и выполняется посредством easy_install или pip:


$ sudo easy_install jsonrpclib
$ sudo pip install jsonrpclib
 	   

Примеры eAPI

Затем мы можем написать некую простую программ с названием eapi_1.py чтобы посмотреть не текст своего отклика:


#!/usr/bin/python2

from __future__ import print_function
from jsonrpclib import Server
import ssl

ssl._create_default_https_context = ssl._create_unverified_context

switch = Server("https://admin:arista@192.168.199.158/command-api")

response = switch.runCmds( 1, [ "show version" ] )
print('Serial Number: ' + response[0]['serialNumber'])
 	   
[Замечание]Замечание

Отметим, что так как это Python 2, я применяю в своём сценарии from __future__ import print_function чтобы сделать миграцию в дальнейшем более простой. Все соответствующие строки ssl предназначены для Python > 2.7.9, за дополнительными сведениями обращайтесь к https://www.python.org/dev/peps/pep-0476/.

Вот тот отклик, который я получил от своего предыдущего метода runCms():


[{u'memTotal': 3978148, u'internalVersion': u'4.16.6M-3205780.4166M', u'serialNumber': u'<пропуск>', u'systemMacAddress': u'', u'bootupTimestamp': 1465964219.71, u'memFree': 277832, u'version': u'4.16.6M', u'modelName': u'DCS-7050QX-32-F', u'isIntlVersion': False, u'internalBuildId': u'373dbd3c-60a7-4736-8d9e-bf5e7d207689', u'hardwareRevision': u'00.00', u'architecture': u'i386'}]
 	   

Как вы можете видеть, это некий список, содержащий один элемент словаря. Если нам необходимо вычленить имеющийся серийный номер, мы можем просто сослаться на номер элемента и имеющийся в строке 12 ключ:


print('Serial Number: ' + response[0]['serialNumber'])
 	   

Полученный вывод будет содержать только сам серийной номер:


$ python eapi_1.py
Serial Number: <пропуск>
 	   

Чтобы дополнительно ознакомиться с руководством по командам, я рекомендую вам кликнуть по ссылке Command Documentation на странице API и сопоставить ваш вывод с выводом version в имеющейся документации.

Как уже отмечалось ранее, в отличии от REST, клиент JSON-RPC использует один и тот же URL конечной точки для вызова всех ресурсов данного сервера. Вы можете почерпнуть из предыдущего примера, что имеющийся метод runCmds() потребляет некий список команд. Для самого исполнения команд настройки вы можете последовать той де самой структуре и поместить необходимый перечень команд внутри второго аргумента.

Вот пример команд настройки, вызываемых eapi_2.py:


#!/usr/bin/python2

from __future__ import print_function
from jsonrpclib import Server
import ssl, pprint

ssl._create_default_https_context = ssl._create_unverified_context

# Выполняем команды Arista через eAPI
def runAristaCommands(switch_object, list_of_commands):
    response = switch_object.runCmds(1, list_of_commands)
    return response

switch = Server("https://admin:arista@192.168.199.158/commandapi")

commands = ["enable", "configure", "interface ethernet 1/3", "switchport acc ess vlan 100", "end", "write memory"]

response = runAristaCommands(switch, commands)
pprint.pprint(response)
 	   

Вот вывод исполнения всех этих команд:


$ python2 eapi_2.py
[{}, {}, {}, {}, {}, {u'messages': [u'Copy completed successfully.']}]
 	   

Теперь выполним быструю проверку в самом коммутаторе чтобы проверить исполнение команд:


arista1#sh run int eth 1/3
interface Ethernet1/3
    switchport access vlan 100
arista1#
 	   

В целом eAPI достаточно прямолинеен и прост в применении. Большинство языков программирования имеют библиотеки, аналогичные jsonrpclib, которые абстрагируются от внутреннего устройства JSON-RPC. При помощи всего нескольких команд вы можете начать интеграцию автоматизации Arista EOS в своей сетевой среде.

Библиотека Arista Pyeapi

Сам клиент Python для имеющихся библиотек eAPI и Pyeapi (http://pyeapi.readthedocs.io/en/master/index.html) является естественной обёрткой вокруг eAPI. Он предоставляет некий набор связываний для настройки узлов Arista EOS. Зачем нам нужен Pyeapi, когда у нас уже имеется eAPI and eAPI? Так как мы должны применять Python для демонстрации eAPI, держите в уме что единственным требованием для eAPI является некий JSON-RPC совместимый клиент. Таким образом, он совместим с большинством языком программирования. Что касается лично меня, я считаю, что eAPI является общим знаменателем по всем различным языкам. Когда я впервые начал работать в полях, доминирующим языком для сценариев и сетевой автоматизации являлся Perl. Имелось большое число корпораций, которые полагались на сценарии Perl как на свои первичные средства автоматизации. Либо в ситуации, когда определённая корпорация уже инвестировала некую тонну ресуросв и кодов, основанных на другом языке, eAPI с JSON-RPC будут наилучшим вариантом чтобы пройти через это.

Однако для тех из нас, кто предпочитает кодировать на Python, естественная библиотека Python означает, более натуральные ощущения при написании нашего кода. Это несомненно делает более простым расширение некоторой программы Python для поддержки нашего EOS узла. Это также уберегает нас от самых последних изменений в Python более простым образом. Например, мы можем применять c Pyeapi Python 3!

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

На момент написания, поддержка Python 3 (3.4+) официально постулировалась как работа в исполнении (http://pyeapi.readthedocs.io/en/master/requirements.html) а документации. Пожалуйста, сверьтесь с имеющейся документацией!

Установка Pyeapi

Установка прямолинейно осуществляется при помощи pip:


$ sudo pip install pyeapi
$ sudo pip3 install pyeapi
 	   
[Замечание]Замечание

Отметим, что pip также установит необходимую библиотеку netaddr, так как она является частью имеющегося состояния (requirements) для Pyeapy.

По умолчанию клиент Pyeapy осуществит поиск некоторого скрытого (с точкой в начале имени) файла в стиле INI с названием eapi.conf в вашем домашнем каталоге. Вы можете изменить такое поведение, определив свой путь к данному файлу eapi.conf, однако это неплохая идея обычно отделять ваши полномочия соединения и блокировать их от самого сценария. Вы можете просмотреть документацию Pyeapy (http://pyeapi.readthedocs.io/en/master/configfile.html#configfile) на предмет описания всех полей, содержащихся в данном файле; вот тот файл, который я применяю в своей лаборатории:


cat ~/.eapi.conf
[connection:Arista1]
host: 192.168.199.158
username: admin
password: arista
transport: https
 	   

Самая первая строка с названием [connection:Arista1] содержит то имя, которое мы будем применять в своём Pyeapy соединении; все оставшиеся поля должны быть понятны сами по себе. Вы можете заблокировать данный файл доступностью только на чтение для тех пользователей, которые намереваются вызывать его:


$ chmod 400 ~/.eapi.conf
$ ls -l ~/.eapi.conf
-r-------- 1 echou echou 94 Jan 27 18:15 /home/echou/.eapi.conf
 	   

Примеры PyEZ

Теперь мы готовы взглянуть на использование. Давайте начнём с подключения к узлу EOS путём создания некоего объекта:


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 pyeapi
>>> arista1 = pyeapi.connect_to('Arista1')
 	   

Мы можем выполнять команды отображения на данном узле и принимать обратно их вывод:


>>> import pprint
>>> pprint.pprint(arista1.enable('show hostname'))
[{'command': 'show hostname',
 'encoding': 'json',
 'result': {'fqdn': 'arista1', 'hostname': 'arista1'}}]
 	   

Само поле настройки может быть либо некоторой отдельной командой, или списком команд, применяющим имеющийся метод config():


>>> arista1.config('hostname arista1-new')
[{}]
>>> pprint.pprint(arista1.enable('show hostname'))
[{'command': 'show hostname',
'encoding': 'json',
'result': {'fqdn': 'arista1-new', 'hostname': 'arista1-new'}}]
>>> arista1.config(['interface ethernet 1/3', 'description my_link'])
[{}, {}]
 	   

Отметим, что сокращение команд (show run вместо show running-config) и некоторые расширения могут не работать:


>>> pprint.pprint(arista1.enable('show run'))
Traceback (most recent call last):
...
 File "/usr/local/lib/python3.5/dist-packages/pyeapi/eapilib.py", line 396, in send 
 raise CommandError(code, msg, command_error=err, output=out) pyeapi.eapilib.CommandError: Error [1002]: CLI command 2 of 2 'show run' failed: invalid command [incomplete token (at token 1: 'run')]
>>>
>>> pprint.pprint(arista1.enable('show running-config interface ethernet 1/3'))
Traceback (most recent call last):
...
pyeapi.eapilib.CommandError: Error [1002]: CLI command 2 of 2 'show runningconfig interface ethernet 1/3' failed: invalid command [incomplete token (at token 2: 'interface')]
 	   

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


>>> result = arista1.enable('show running-config')
>>> pprint.pprint(result[0]['result']['cmds']['interface Ethernet1/3']) {'cmds': {'description my_link': None, 'switchport access vlan 100': None}, 'comments': []}
 	   

До сих пор мы выполняли то, что мы делали с eAPI для команд отображения и настройки. Pyeapi предлагает различные API чтобы сделать жизнь проще. В приводимом ниже примере мы соединимся с нужным узлом, вызовем VLAN API и начнём работать с имеющимися у этого устройства параметрами VLAN. Давайте взглянем на это:


>>> import pyeapi
>>> node = pyeapi.connect_to('Arista1')
>>> vlans = node.api('vlans')
>>> type(vlans)
<class 'pyeapi.api.vlans.Vlans'>
>>> dir(vlans)
[...'command_builder', 'config', 'configure', 'configure_interface', 'configure_vlan', 'create', 'default', 'delete', 'error', 'get', 'get_block', 'getall', 'items', 'keys', 'node', 'remove_trunk_group', 'set_name', 'set_state', 'set_trunk_groups', 'values']
>>> vlans.getall()
{'1': {'vlan_id': '1', 'trunk_groups': [], 'state': 'active', 'name': 'default'}}
>>> vlans.get(1)
{'vlan_id': 1, 'trunk_groups': [], 'state': 'active', 'name': 'default'}
>>> vlans.create(10)
True
>>> vlans.getall()
{'1': {'vlan_id': '1', 'trunk_groups': [], 'state': 'active', 'name': 'default'}, '10': {'vlan_id': '10', 'trunk_groups': [], 'state': 'active', 'name': 'VLAN0010'}}
>>> vlans.set_name(10, 'my_vlan_10')
True
 	   

Давайте проверим VLAN 10, которую мы создали при помощи VLAN API в данном устройстве:


arista1#sh vlan
VLAN Name Status Ports
----- -------------------------------- --------- -------------------------------
1 default active
10 my_vlan_10 active
 	   

Как вы можете видеть, естественный API Python во всех объектах EOS в действительности даёт превосходство Pyeapy над eAPI, так как он абстрагируется от признаков самого нижнего уровня в объекте данного устройства.

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

Для полного перечня даже ещё большего превосходства Pyeapy над eAPI cверьтесь c официальной документацей: (http://pyeapi.readthedocs.io/en/master/api_modules/_list_of_modules.html).

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


#!/usr/bin/env python3

import pyeapi

class my_switch():

    def __init__(self, config_file_location, device):
        # загружаем свой файл настроек
        pyeapi.client.load_config(config_file_location)
        self.node = pyeapi.connect_to(device)
        self.hostname = self.node.enable('show hostname')[0] ['result']['host name']
        self.running_config = self.node.enable('show runningconfig')

    def create_vlan(self, vlan_number, vlan_name):
        vlans = self.node.api('vlans')
        vlans.create(vlan_number)
        vlans.set_name(vlan_number, vlan_name)
 	   

Как вы можете увидеть, мы автоматически подключились к требуемому узлу и установили необходимые имя хоста и running_config на протяжении данного соединения. Мы также создали некий метод для данного класса, который создаёт VLAN при помощи VLAN API. Давайте попробуем его:


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 pyeapi_1
>>> s1 = pyeapi_1.my_switch('/tmp/.eapi.conf', 'Arista1')
>>> s1.hostname
'arista1'
>>> s1.running_config
[{'encoding': 'json', 'result': {'cmds': {'interface Ethernet27': {'cmds': {}, 'comments': []}, 'ip routing': None, 'interface face Ethernet29': {'cmds': {}, 'comments': []}, 'interface Ethernet26': {'cmds': {}, 'comments': []}, 'interface Ethernet24/4': h.':
<пропуск>
'interface Ethernet3/1': {'cmds': {}, 'comments': []}}, 'comments': [], 'header': ['! device: arista1 (DCS-7050QX-32, EOS-4.16.6M)n!n']}, 'command': 'show running-config'}]
>>> s1.create_vlan(11, 'my_vlan_11')
>>> s1.node.api('vlans').getall()
{'11': {'name': 'my_vlan_11', 'vlan_id': '11', 'trunk_groups': [], 'state': 'active'}, '10': {'name': 'my_vlan_10', 'vlan_id': '10', 'trunk_groups': [], 'state': 'active'}, '1': {'name': 'default', 'vlan_id': '1', 'trunk_groups': [], 'state': 'active'}}
>>> 
 	   

Нейтральные к производителю библиотеки

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

Выводы

В данной главе мы рассмотрели различные способы взаимодействия с сетевыми устройствами от Cisco, Juniper и Arista, а также управления ими. Мы рассмотрели оба способа взаимодействия, и NETCONF, и REST, а также применение создаваемых самим производителем библиотек, таких как PyEZ и Pyeapi. Имеются различные уровни абстракции, что означает предоставление некоторого способа программно управлять вашими сетевыми устройствами без вмешательства со стороны персонала.

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