Программирование для сетевых инженеров: первый кейс
Использование программирования в сетевом деле уже стало трендом, поэтому в продолжении статьи Зачем сетевым инженерам программирование я начинаю серию небольших заметок про автоматизацию решения тех или иных практических задач. Чтобы развеять ореол сложности вокруг этой темы, будут опубликованы некоторые примеры и кейсы, в основном с использованием Python, и даны ссылки на более глубокий материал и техническую документацию. Вступительная статья этого цикла ниже.
Сначала пара слов для антагонистов, не спешите говорить «это не для меня». Тенденции, происходящие в индустрии сетей передачи данных, отодвигают вопрос «как сделать» на второй план, перемещая на первый план вопрос эффективной, т.е. безошибочной и не затратной, эксплуатации, эти же тенденции толкают нас, сетевых инженеров, к изучению различных средств автоматизации. Тем, чей разум не «заражен» вирусом под названием указатель на указатель на функцию, я предлагаю начать этот путь с Python, хорошим подспорьем в этом деле может послужить книга Automate the Boring Stuff with Python. Книга написана в очень дружественной для новичков форме, и прекрасно подойдет для получения необходимого минимума знаний и практического опыта. Материал глав, примеры и задания соответствуют духу и философии python:
- Красивое лучше, чем уродливое.
- Простое лучше, чем сложное.
- Сложное лучше, чем запутанное.
- Практичность важнее безупречности.
Прочитав меньше половины глав, вы поймете структуры данных python и сможете писать первый код, а прочитав вторую половину вы получите представление о возможностях python в решении прикладных задач, наподобие отправки HTTP запросов, работы с данными в формате CSV или разборе документов JSON. Я сторонник изучения программирования по схеме от низкоуровневого к высокоуровневому, однако ознакомившись с книгой, я заметил, что повествование само собой подталкивает к понимаю азов программирования с такой же простотой и естественностью, с которой дети начинают общаться на родном языке. Если вы не являетесь профессиональным программистом, а просто подыскиваете литературу чтобы научится формулировать свои мысли в виде кода, попробуйте сделать python своим родным языком.
Итак, первый пример посвящен проверке operation состояний маршрутизатора Juniper Networks. В качестве опорной я выбрал задачу проверки наличия на удаленной стороне ответного плеча для сконфигурированных RSVP LSP. Наличие двустороннего MPLS транспорта является обязательным условием для передачи трафика различного рода VPN. Сервисная сигнализация вполне может работать по IP, а data-plane трафику необходимы бесшовные MPLS пути между PE маршрутизаторами. Для проверки LSP путей из CLI мы обычно используем команду
display mpls lsp
В данном случае нам требуется убедиться в том, что для каждой LSP из Ingress секции существует такая LSP из Egress секции, у которой адрес назначения равен адресу источника первой LSP.
Мы решим эту задачу с помощью Pyez, этот мини фреймворк содержит набор классов и структур данных для взаимодействия с маршрутизаторами из Python кода. Вот тут более детальное описание возможностей Understanding Junos PyEZ, и процедура установки Junos PyEZ
Pyez использует возможности Junos по преобразованию формата выводы в XML. Добавьте к любой команде опцию
| display xml
и вы получите готовый интерфейс взаимодействия по каналу машина-машина. Более детально об этой возможности можно прочитать в XML and Junos OS Overview
Именно в таком виде Pyez получает данные с маршрутизатора, а так как мне не доставляет особенного удовольствия работа с сырыми XML данными, я расскажу как представления (View) и таблицы (Table) позволяют абстрагироваться от тонкостей этого формата.
Каждой команде Junos соответствует некоторый метод Pyez, чтобы узнать имя этого метода используйте опцию | display xml rpc
. Имя метода находится прямо внутри тегов , в данном случае это get-mpls-lsp-information
user@host> show mpls lsp | display xml rpc
;
С форматом вывода методов и соответствующих им команд, можно ознакомится получив ответ в XML виде, или изучить его на страничке XML API Explorer — Operational Tags Применительно к команде get-mpls-lsp-information
маршрутизатор ответит следующим образом:
....
session-type
count
display-count
up-count
down-count
detours
....
....
destination-address
is-detour
source-address
lsp-state
lsp-pktbytes
bypass-name
no-statistics
route-count
rsb-count
resv-style
label-in
label-out
name
mpls-p2mp-lsp-name
p2mp-remerge-state
lsp-description
lsp-path-type
mpls-lsp-type
lsp-aggregation
graceful-deletion-triggered
source-tna-address
destination-tna-address
bidirectional
associated-bidirectional
lsp-associated-lspname
lsp-associated-lspsrc
upstream-label-in
upstream-label-out
suggested-label-in
suggested-label-out
recovery-label-in
recovery-label-out
psb-lifetime
psb-creation-time
path-mtu
path-mtu-in-kernel
sender-tspec
....
adspec
ct-bw
lsp-diffserv-info
lsp-id
tunnel-id
proto-id
p2mp-branch-id
p2mp-subgroup-orig
self-id
p2mp-self-id
session-id
is-fastreroute
is-linkprotection
is-nodeprotection
is-soft-preemption
rsvp-path-status
rsvp-lp-backup-route-cnt
rsvp-lp-backup-lsp-cnt
....
....
....
....
....
....
....
....
....
....
....
destination-address
source-address
lsp-state
route-count
active-path
is-primary
name
bidirectional
associated-bidirectional
lsp-associated-lspname
lsp-associated-lspsrc
lsp-description
lsp-pktbytes
lsp-packets
lsp-bytes
aggregate-lsp-pktbytes
no-statistics
mpls-p2mp-name
lsp-type
lsp-control-status
egress-label-operation
is-fastreroute
is-linkprotection
is-nodeprotection
is-inter-domain-path
load-balance
lsp-diffserv-te-info
metric
revert-timer
revert-timer-remain
optimize-protection-timer
....
....
....
lsp-creation-time
lsp-soft-preemption-counter
lsp-soft-preemption-time
retry-timer
retry-limit
....
....
....
XML документ состоит из элементов, которые могу содержать дочерние элементы или атомарные значения. Например, элемент
содержит дочерние элементы
, которые в свою очередь содержат элементы
, внутри которых есть элемент
с атомарными значениями типа name
.
Чтобы организовать цикл по вложенным структурам такого рода, я использую представления и таблицы. Этот подход опирается на динамическую типизацию Python для создания массивов или списков словарей на этапе выполнения. Если элемент, например
, содержит некоторое количество под-элементов
, вы получите их список, а если под-элемент, например
уникален, вы получите его значение в виде объекта. Более детально о таблицах и представлениях написано в Defining Junos PyEZ Operational Tables и в Defining Junos PyEZ Views for Operational Tables.
Заполнение формата представлений и таблиц в коде примера осуществляется в строковой переменной yml
, ключевые элементы этого формата ниже:
Значение rpc используется в таблице, и содержит имя метода из display xml rpc
вывода.
Значение item используется в таблице, и содержит имя элемента XML, который нас интересует. Имена задаются с учетом иерархии пути в XML документе.
Значение view используется в таблице, и содержит описание представления. В случае наличия вложенных элементов, вы должны сделать вложенные описания.
Значение fields используется в представлении и содержит имена атомарных элементов.
Вот, пожалуй, все, что нужно знать о Pyez для организации итерации по плоским и вложенным operational данным маршрутизатора, завершенный пример ниже.
import sys
import yaml
from jnpr.junos.factory.factory_loader import FactoryLoader
from jnpr.junos import Device
yml = '''
---
MplsSession:
rpc: get-mpls-lsp-information
item: rsvp-session-data
view: MplsSessionView
MplsSessionView:
fields:
type: session-type
count: count
lsp: _MplsLsp
rsvp: _RsvpLsp
_RsvpLsp:
item: rsvp-session
view: _MplsLspView
_MplsLsp:
item: rsvp-session/mpls-lsp
view: _MplsLspView
_MplsLspView:
fields:
dst_addr: destination-address
src_addr: source-address
state: lsp-state
route_count: route-count
active_path: active-path
name: name
'''
globals().update(FactoryLoader().load(yaml.load(yml)))
if (len(sys.argv) < 4):
print 'Call this script as ' + sys.argv[0] + ' host user password '
sys.exit()
try:
host = sys.argv[1]
user = sys.argv[2]
password = sys.argv[3]
dev = Device(host=host, user=user, password=password, mode='telnet', port='23')
dev.open()
except Exception:
print 'Cannot connect to ' + host
sys.exit()
bt = MplsSession(dev).get()
dev.close()
out=''
for s in bt:
if (s.type == 'Ingress'):
for l in s.lsp:
bidir = 0
for ss in bt:
if (ss.type == 'Egress'):
for r in ss.rsvp:
if ( (r.dst_addr == l.src_addr) and (r.src_addr == l.dst_addr) ):
bidir = 1
out = 'Remote LSP named ' + r.name
if (bidir == 0):
print 'Unidirectional LSP named ' + l.name + ' to: ' + l.dst_addr + ' is in ' + l.state + ' state'
if (bidir == 1):
print 'Bidirectional LSP named ' + l.name + ' to: ' + l.dst_addr + ' is in ' + l.state + ' state'
print out
print ''