Сейчас очень популярна тема сетевой автоматизации. Одним из инструментов такой автоматизации в мире Juniper является библиотека PyEZ, разрабатываемая командой Джереми Шульмана (Jeremy Schulman). PyEZ — это микро-фреймворк для удаленного управления и автоматизации устройств Juniper, написанный на языке Python. Основным преимуществом PyEZ является его простота и нацеленность на аудиторию сетевых инженеров, а не программистов.
Некоторые возможности PyEZ:
В этой статье я бы хотел остановиться на возможности извлечения оперативной информации на примере коммутатора EX4200.
На данном этапе все должно быть готово к работе. Для проверки можно попробовать подключиться к устройству и снять базовую информацию.
Поясню, что здесь происходит. Сначала импортируются два класса: стандартный pprint для форматированного вывода и Device из библиотеки PyEZ. Далее объявляется само устройство, к которому будем подключаться. При объявлении устройства достаточно указать только IP-адрес, тогда будет применена аутентификация по ключу, а имя пользователя будет взято из переменной среды $USER. Опционально можно указать имя пользователя и пароль для аутентицикации по паролю (подробнее опять же на вики). Затем идет подключение к устройству и сбор «фактов», которые выводятся с помощью pprint, после чего подключение закрывается.
Теперь можно перейти к более интересным примерам использования PyEZ.
Рассмотрим задачу опроса коммутаторов и снятия информации о топологии Spanning Tree. Грубо говоря, нужно пройтись по коммутаторам и вывести список портов каждого коммутатора с указанием состояния STP.
Посмотрим как получить такую информацию используя CLI.
Теперь посмотрим, как это выглядит «на самом деле», т.е. в XML.
Тут стоит сделать отступление и рассказать о подходе, используемом PyEZ для извлечения конфигурационной и оперативной (running state) информации из устройств. Для этого вводятся понятия таблиц (Tables) и выборок (Views). Если проводить аналогию с базами данных, то в Junos существует «оперативная база данных», которая состоит из набора таблиц. Например, командой show route можно посмотреть таблицу маршрутизации, а командой show interfaces — таблицу интерфейсов. В свою очередь Views, т.е. выборки, это способ представления информации из таблиц. Например, show route protocol ospf покажет только маршруты полученные по OSPF, т.е. выборку из всей таблицы маршрутизации.
Вернемся к полученному XML. Нас интересуют элементы <stp-interface-entry> и входящие в них <interface-name>, <port-state> и <port-role>. Чтобы получить доступ к этим элементам, нужно определить таблицу и выборку. Это делается в отдельном YAML-файле.
Пройдемся по содержимому:
Наконец, в STPInterfacesView определяем интересующие поля.
На мой взгляд, правильно составить YAML-файл — это самое сложное во всем описываемом процессе. Очень рекомендую почитать туториал по XPath.
Теперь посмотрим, как это можно использовать в Python. Предположим, что YAML-файл был сохранен как stp.yml.
Получаем тот же список портов, как и в CLI.
Некоторые возможности PyEZ:
- Сбор «фактов», таких как серийный номер, версия ОС и т.д.
- Извлечение оперативной (аналог команд show) информации
- Извлечение конфигурации
- Изменение конфигурации
В этой статье я бы хотел остановиться на возможности извлечения оперативной информации на примере коммутатора EX4200.
Подготовка к работе
- Установка самого фреймворка сводится к одной команде:
pip install junos-eznc
На моей Ubuntu 14.04 все установилось без проблем, но если вдруг — рекомендую почитать официальную вики-страничку.
- Для доступа на сетевые устройства PyEZ использует протокол NETCONF, который необходимо включить.
set system services netconf ssh
NETCONF в свою очередь работает поверх SSH, поэтому убедитесь что SSH настроен и создана учетная запись для доступа на устройство.
На данном этапе все должно быть готово к работе. Для проверки можно попробовать подключиться к устройству и снять базовую информацию.
dteslya@ubuntu:~$ python
Python 2.7.6 (default, Mar 22 2014, 22:59:56)
[GCC 4.8.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from pprint import pprint
>>> from jnpr.junos import Device
>>> dev = Device(host='1.2.3.4')
>>> dev
Device(1.2.3.4)
>>> dev.open()
Device(1.2.3.4)
>>> pprint(dev.facts)
{'2RE': True,
'HOME': '/var/home/dteslya',
'RE0': {'last_reboot_reason': 'Router rebooted after a normal shutdown.',
'mastership_state': 'master',
'model': 'EX4200-24F',
'status': 'OK',
'up_time': '2 days, 3 hours, 34 minutes, 40 seconds'},
'domain': None,
'fqdn': 'SW1',
'hostname': 'SW1',
'ifd_style': 'SWITCH',
'master': 'RE0',
'model': 'EX4200-24F',
'personality': 'SWITCH',
'serialnumber': '',
'switch_style': 'VLAN',
'vc_capable': True,
'version': '12.3R8.7',
'version_RE0': '12.3R8.7',
'version_info': junos.version_info(major=(12, 3), type=R, minor=8, build=7)}
>>> dev.close()
>>>
Поясню, что здесь происходит. Сначала импортируются два класса: стандартный pprint для форматированного вывода и Device из библиотеки PyEZ. Далее объявляется само устройство, к которому будем подключаться. При объявлении устройства достаточно указать только IP-адрес, тогда будет применена аутентификация по ключу, а имя пользователя будет взято из переменной среды $USER. Опционально можно указать имя пользователя и пароль для аутентицикации по паролю (подробнее опять же на вики). Затем идет подключение к устройству и сбор «фактов», которые выводятся с помощью pprint, после чего подключение закрывается.
Теперь можно перейти к более интересным примерам использования PyEZ.
Постановка задачи
Рассмотрим задачу опроса коммутаторов и снятия информации о топологии Spanning Tree. Грубо говоря, нужно пройтись по коммутаторам и вывести список портов каждого коммутатора с указанием состояния STP.
Junos CLI
Посмотрим как получить такую информацию используя CLI.
dteslya@SW1> show spanning-tree interface
Spanning tree interface parameters for instance 0
Interface Port ID Designated Designated Port State Role
port ID bridge ID Cost
ge-0/0/23.0 128:536 128:536 32768.54e032fdeb41 20000 DIS DIS
xe-0/1/0.0 128:561 128:691 32768.3c94d5902981 2000 FWD ROOT
xe-0/1/2.0 128:563 128:563 32768.54e032fdeb41 2000 FWD DESG
{master:0}
Junos XML
Теперь посмотрим, как это выглядит «на самом деле», т.е. в XML.
show spanning-tree interface | display xml
dteslya@SW1> show spanning-tree interface | display xml
<rpc-reply xmlns:junos="http://xml.juniper.net/junos/12.3R8/junos">
<stp-interface-information>
<stp-instance>
<instance-id>0</instance-id>
<stp-interfaces junos:style="brief">
<stp-interface-entry>
<interface-name>ge-0/0/23.0</interface-name>
<port-number junos:format="128:536">
536
</port-number>
<port-priority>128</port-priority>
<designated-port-number junos:format="128:536">
536
</designated-port-number>
<designated-port-priority>128</designated-port-priority>
<port-cost>20000</port-cost>
<port-state>DIS</port-state>
<designated-bridge-mac junos:format="32768.54e032fdeb41">
54e032fdeb41
</designated-bridge-mac>
<designated-bridge-priority>32768</designated-bridge-priority>
<port-role>DIS</port-role>
</stp-interface-entry>
<stp-interface-entry>
<interface-name>xe-0/1/0.0</interface-name>
<port-number junos:format="128:561">
561
</port-number>
<port-priority>128</port-priority>
<designated-port-number junos:format="128:691">
691
</designated-port-number>
<designated-port-priority>128</designated-port-priority>
<port-cost>2000</port-cost>
<port-state>FWD</port-state>
<designated-bridge-mac junos:format="32768.3c94d5902981">
3c94d5902981
</designated-bridge-mac>
<designated-bridge-priority>32768</designated-bridge-priority>
<port-role>ROOT</port-role>
</stp-interface-entry>
<stp-interface-entry>
<interface-name>xe-0/1/2.0</interface-name>
<port-number junos:format="128:563">
563
</port-number>
<port-priority>128</port-priority>
<designated-port-number junos:format="128:563">
563
</designated-port-number>
<designated-port-priority>128</designated-port-priority>
<port-cost>2000</port-cost>
<port-state>FWD</port-state>
<designated-bridge-mac junos:format="32768.54e032fdeb41">
<designated-port-priority>128</designated-port-priority>
<port-cost>20000</port-cost>
<port-state>DIS</port-state>
<designated-bridge-mac junos:format="32768.54e032fdeb41">
54e032fdeb41
</designated-bridge-mac>
<designated-bridge-priority>32768</designated-bridge-priority>
<port-role>DIS</port-role>
</stp-interface-entry>
<stp-interface-entry>
<interface-name>xe-0/1/0.0</interface-name>
<port-number junos:format="128:561">
561
</port-number>
<port-priority>128</port-priority>
<designated-port-number junos:format="128:691">
691
</designated-port-number>
<designated-port-priority>128</designated-port-priority>
<port-cost>2000</port-cost>
<port-state>FWD</port-state>
<designated-bridge-mac junos:format="32768.3c94d5902981">
3c94d5902981
</designated-bridge-mac>
<designated-bridge-priority>32768</designated-bridge-priority>
<port-role>ROOT</port-role>
</stp-interface-entry>
<stp-interface-entry>
<interface-name>xe-0/1/2.0</interface-name>
<port-number junos:format="128:563">
563
</port-number>
<port-priority>128</port-priority>
<designated-port-number junos:format="128:563">
563
</designated-port-number>
<designated-port-priority>128</designated-port-priority>
<port-cost>2000</port-cost>
<port-state>FWD</port-state>
<designated-bridge-mac junos:format="32768.54e032fdeb41">
54e032fdeb41
</designated-bridge-mac>
<designated-bridge-priority>32768</designated-bridge-priority>
<port-role>DESG</port-role>
</stp-interface-entry>
</stp-interfaces>
</stp-instance>
</stp-interface-information>
<cli>
<banner>{master:0}</banner>
</cli>
</rpc-reply>
{master:0}
Тут стоит сделать отступление и рассказать о подходе, используемом PyEZ для извлечения конфигурационной и оперативной (running state) информации из устройств. Для этого вводятся понятия таблиц (Tables) и выборок (Views). Если проводить аналогию с базами данных, то в Junos существует «оперативная база данных», которая состоит из набора таблиц. Например, командой show route можно посмотреть таблицу маршрутизации, а командой show interfaces — таблицу интерфейсов. В свою очередь Views, т.е. выборки, это способ представления информации из таблиц. Например, show route protocol ospf покажет только маршруты полученные по OSPF, т.е. выборку из всей таблицы маршрутизации.
YAML
Вернемся к полученному XML. Нас интересуют элементы <stp-interface-entry> и входящие в них <interface-name>, <port-state> и <port-role>. Чтобы получить доступ к этим элементам, нужно определить таблицу и выборку. Это делается в отдельном YAML-файле.
---
STPInterfaces:
rpc: get-stp-bridge-interface-information
item: stp-instance/stp-interfaces/stp-interface-entry
key: interface-name
view: STPInterfacesView
STPInterfacesView:
fields:
state: port-state
role: port-role
Пройдемся по содержимому:
- STPInterfaces — название таблицы. Может быть любым.
- rpc — команда, посылаемая устройству. Можно посмотреть с помощью display-фильтра xml rpc в Junos CLI:
dteslya@SW1> show spanning-tree interface | display xml rpc <rpc-reply xmlns:junos="http://xml.juniper.net/junos/12.3R8/junos"> <rpc> <get-stp-bridge-interface-information> </get-stp-bridge-interface-information> </rpc> <cli> <banner>{master:0}</banner> </cli> </rpc-reply>
- item — интересующий элемент таблицы. Указывается с помощью XPath относительно первого элемента следующего после тэга <rpc-reply>, т.е. <stp-interface-information> в нашем случае.
- key — ключ, с помощью которого можно ссылаться на интересующие записи в таблице. В нашем случае ключем будет <interface-name>.
- view — ссылка на выборку, которая извлекает данные из элемента таблицы, определенного ранее.
- STPInterfacesView — название выборки. Может быть любым.
Наконец, в STPInterfacesView определяем интересующие поля.
На мой взгляд, правильно составить YAML-файл — это самое сложное во всем описываемом процессе. Очень рекомендую почитать туториал по XPath.
Python shell
Теперь посмотрим, как это можно использовать в Python. Предположим, что YAML-файл был сохранен как stp.yml.
Python 2.7.6 (default, Mar 22 2014, 22:59:56)
[GCC 4.8.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from jnpr.junos import Device
>>> from jnpr.junos.factory import loadyaml
>>> yml_file = "stp.yml"
>>> globals().update(loadyaml(yml_file))
>>> dev = Device(host='1.2.3.4')
>>> dev.open()
Device(1.2.3.4)
>>> tbl = STPInterfaces(dev)
>>> tbl.get()
STPInterfaces:1.2.3.4: 3 items
>>> for key in tbl:
... print key.name, key.role, key.state
...
ge-0/0/23.0 DIS DIS
xe-0/1/0.0 ROOT FWD
xe-0/1/2.0 DESG FWD
>>> dev.close()
>>>
Получаем тот же список портов, как и в CLI.