Программирование для сетевых инженеров: первый кейс

habr.png

Использование программирования в сетевом деле уже стало трендом, поэтому в продолжении статьи Зачем сетевым инженерам программирование я начинаю серию небольших заметок про автоматизацию решения тех или иных практических задач. Чтобы развеять ореол сложности вокруг этой темы, будут опубликованы некоторые примеры и кейсы, в основном с использованием 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

show mpls lsp | display xml rpc
user@host> show mpls lsp | display xml rpc
;
   
       
       
   



С форматом вывода методов и соответствующих им команд, можно ознакомится получив ответ в XML виде, или изучить его на страничке XML API Explorer — Operational Tags Применительно к команде get-mpls-lsp-information маршрутизатор ответит следующим образом:

show mpls lsp | display xml
    
       ....
     

     
       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 ''

© Habrahabr.ru