Использование программирования в сетевом деле уже стало трендом, поэтому в продолжении статьи Зачем сетевым инженерам программирование я начинаю серию небольших заметок про автоматизацию решения тех или иных практических задач. Чтобы развеять ореол сложности вокруг этой темы, будут опубликованы некоторые примеры и кейсы, в основном с использованием 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
<rpc-reply xmlns:junos="http://xml.juniper.net/junos/15.1R1/j...">;
   <rpc>
       <get-mpls-lsp-information>
       </get-mpls-lsp-information>
   </rpc>
</rpc-reply>


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

show mpls lsp | display xml
    <rsvp-session-information>
       <rsvp-session-data>....</rsvp-session-data>
     </rsvp-session-information>

     <rsvp-session-data>
       <session-type><i>session-type</i></session-type>
       <count><i>count</i></count>
       <display-count><i>display-count</i></display-count>
       <up-count><i>up-count</i></up-count>
       <down-count><i>down-count</i></down-count>
       <detours><i>detours</i></detours>
       <rsvp-session>....</rsvp-session>
       <mpls-p2mp-lsp>....</mpls-p2mp-lsp>
     </rsvp-session-data>

     <rsvp-session>
       <destination-address><i>destination-address</i></destination-address>
       <is-detour><i>is-detour</i></is-detour>
       <source-address><i>source-address</i></source-address>
       <lsp-state><i>lsp-state</i></lsp-state>
       <lsp-pktbytes><i>lsp-pktbytes</i></lsp-pktbytes>
       <bypass-name><i>bypass-name</i></bypass-name>
       <no-statistics><i>no-statistics</i></no-statistics>
       <route-count><i>route-count</i></route-count>
       <rsb-count><i>rsb-count</i></rsb-count>
       <resv-style><i>resv-style</i></resv-style>
       <label-in><i>label-in</i></label-in>
       <label-out><i>label-out</i></label-out>
       <name><i>name</i></name>
       <mpls-p2mp-lsp-name><i>mpls-p2mp-lsp-name</i></mpls-p2mp-lsp-name>
       <p2mp-remerge-state><i>p2mp-remerge-state</i></p2mp-remerge-state>
       <lsp-description><i>lsp-description</i></lsp-description>
       <lsp-path-type><i>lsp-path-type</i></lsp-path-type>
       <mpls-lsp-type><i>mpls-lsp-type</i></mpls-lsp-type>
       <lsp-aggregation><i>lsp-aggregation</i></lsp-aggregation>
       <graceful-deletion-triggered><i>graceful-deletion-triggered</i></graceful-deletion-triggered>
       <source-tna-address><i>source-tna-address</i></source-tna-address>
       <destination-tna-address><i>destination-tna-address</i></destination-tna-address>
       <bidirectional><i>bidirectional</i></bidirectional>
       <associated-bidirectional><i>associated-bidirectional</i></associated-bidirectional>
       <lsp-associated-lspname><i>lsp-associated-lspname</i></lsp-associated-lspname>
       <lsp-associated-lspsrc><i>lsp-associated-lspsrc</i></lsp-associated-lspsrc>
       <upstream-label-in><i>upstream-label-in</i></upstream-label-in>
       <upstream-label-out><i>upstream-label-out</i></upstream-label-out>
       <suggested-label-in><i>suggested-label-in</i></suggested-label-in>
       <suggested-label-out><i>suggested-label-out</i></suggested-label-out>
       <recovery-label-in><i>recovery-label-in</i></recovery-label-in>
       <recovery-label-out><i>recovery-label-out</i></recovery-label-out>
       <psb-lifetime><i>psb-lifetime</i></psb-lifetime>
       <psb-creation-time><i>psb-creation-time</i></psb-creation-time>
       <path-mtu><i>path-mtu</i></path-mtu>
       <path-mtu-in-kernel><i>path-mtu-in-kernel</i></path-mtu-in-kernel>
       <sender-tspec><i>sender-tspec</i></sender-tspec>
       <layer2-tspec>....</layer2-tspec>
       <adspec><i>adspec</i></adspec>
       <ct-bw><i>ct-bw</i></ct-bw>
       <lsp-diffserv-info><i>lsp-diffserv-info</i></lsp-diffserv-info>
       <lsp-id><i>lsp-id</i></lsp-id>
       <tunnel-id><i>tunnel-id</i></tunnel-id>
       <proto-id><i>proto-id</i></proto-id>
       <p2mp-branch-id><i>p2mp-branch-id</i></p2mp-branch-id>
       <p2mp-subgroup-orig><i>p2mp-subgroup-orig</i></p2mp-subgroup-orig>
       <self-id><i>self-id</i></self-id>
       <p2mp-self-id><i>p2mp-self-id</i></p2mp-self-id>
       <session-id><i>session-id</i></session-id>
       <is-fastreroute><i>is-fastreroute</i></is-fastreroute>
       <is-linkprotection><i>is-linkprotection</i></is-linkprotection>
       <is-nodeprotection><i>is-nodeprotection</i></is-nodeprotection>
       <is-soft-preemption><i>is-soft-preemption</i></is-soft-preemption>
       <rsvp-path-status><i>rsvp-path-status</i></rsvp-path-status>
       <rsvp-lp-backup-route-cnt><i>rsvp-lp-backup-route-cnt</i></rsvp-lp-backup-route-cnt>
       <rsvp-lp-backup-lsp-cnt><i>rsvp-lp-backup-lsp-cnt</i></rsvp-lp-backup-lsp-cnt>
       <packet-information>....</packet-information>
       <explicit-route>....</explicit-route>
       <record-route>....</record-route>
       <lsp-attribute-flags>....</lsp-attribute-flags>
       <lp-history>....</lp-history>
       <rsvp-telink>....</rsvp-telink>
       <protection-attribute>....</protection-attribute>
       <association-attribute>....</association-attribute>
       <detour>....</detour>
       <detour-branch>....</detour-branch>
       <mpls-lsp>....</mpls-lsp>
     </rsvp-session>

         <mpls-lsp>
           <destination-address><i>destination-address</i></destination-address>
           <source-address><i>source-address</i></source-address>
           <lsp-state><i>lsp-state</i></lsp-state>
           <route-count><i>route-count</i></route-count>
           <active-path><i>active-path</i></active-path>
           <is-primary><i>is-primary</i></is-primary>
           <name><i>name</i></name>
           <bidirectional><i>bidirectional</i></bidirectional>
           <associated-bidirectional><i>associated-bidirectional</i></associated-bidirectional>
           <lsp-associated-lspname><i>lsp-associated-lspname</i></lsp-associated-lspname>
           <lsp-associated-lspsrc><i>lsp-associated-lspsrc</i></lsp-associated-lspsrc>
           <lsp-description><i>lsp-description</i></lsp-description>
           <lsp-pktbytes><i>lsp-pktbytes</i></lsp-pktbytes>
           <lsp-packets><i>lsp-packets</i></lsp-packets>
           <lsp-bytes><i>lsp-bytes</i></lsp-bytes>
           <aggregate-lsp-pktbytes><i>aggregate-lsp-pktbytes</i></aggregate-lsp-pktbytes>
           <no-statistics><i>no-statistics</i></no-statistics>
           <mpls-p2mp-name><i>mpls-p2mp-name</i></mpls-p2mp-name>
           <lsp-type><i>lsp-type</i></lsp-type>
           <lsp-control-status><i>lsp-control-status</i></lsp-control-status>
           <egress-label-operation><i>egress-label-operation</i></egress-label-operation>
           <is-fastreroute><i>is-fastreroute</i></is-fastreroute>
           <is-linkprotection><i>is-linkprotection</i></is-linkprotection>
           <is-nodeprotection><i>is-nodeprotection</i></is-nodeprotection>
           <is-inter-domain-path><i>is-inter-domain-path</i></is-inter-domain-path>
           <load-balance><i>load-balance</i></load-balance>
           <lsp-diffserv-te-info><i>lsp-diffserv-te-info</i></lsp-diffserv-te-info>
           <metric><i>metric</i></metric>
           <revert-timer><i>revert-timer</i></revert-timer>
           <revert-timer-remain><i>revert-timer-remain</i></revert-timer-remain>
           <optimize-protection-timer><i>optimize-protection-timer</i></optimize-protection-timer>
           <admin-groups>....</admin-groups>
           <admin-groups-extended>....</admin-groups-extended>
           <mpls-srlg>....</mpls-srlg>
           <lsp-creation-time><i>lsp-creation-time</i></lsp-creation-time>
           <lsp-soft-preemption-counter><i>lsp-soft-preemption-counter</i></lsp-soft-preemption-counter>
           <lsp-soft-preemption-time><i>lsp-soft-preemption-time</i></lsp-soft-preemption-time>
           <retry-timer><i>retry-timer</i></retry-timer>
           <retry-limit><i>retry-limit</i></retry-limit>
           <mpls-lsp-autobandwidth>....</mpls-lsp-autobandwidth>
           <mpls-lsp-path>....</mpls-lsp-path>
           <mpls-lsp-attributes>....</mpls-lsp-attributes>
         </mpls-lsp>


XML документ состоит из элементов, которые могу содержать дочерние элементы или атомарные значения. Например, элемент <rsvp-session-information> </rsvp-session-information> содержит дочерние элементы <rsvp-session-data></rsvp-session-data>, которые в свою очередь содержат элементы <rsvp-session>, внутри которых есть элемент <mpls-lsp> с атомарными значениями типа name.

Чтобы организовать цикл по вложенным структурам такого рода, я использую представления и таблицы. Этот подход опирается на динамическую типизацию Python для создания массивов или списков словарей на этапе выполнения. Если элемент, например <rsvp-session-data>, содержит некоторое количество под-элементов <rsvp-session>, вы получите их список, а если под-элемент, например <destination-address> уникален, вы получите его значение в виде объекта. Более детально о таблицах и представлениях написано в 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 ''

Комментарии (4)


  1. heos_spb
    03.05.2018 11:15

    Наличие двустороннего MPLS транспорта является обязательным условием для передачи трафика различного рода VPN


    Простите, что?


    1. a_andreev Автор
      03.05.2018 11:18

      Привет,
      это как в известном тосте о совпадение желаний и возможностей.
      В данном случае «имею желание» соответствует установленному через IP связность сигналингу сервиса, но «не имею возможности» — прерыванию передачи MPLS трафика на уровне data-plane где-то посредине. Как-то так простыми словами.


      1. Xell79
        04.05.2018 08:27

        Вероятно, Вы забыли про то, что не всем VPN нужен MPLS. Некоторым и IP достаточно. Да и такой легаси, как PPPoE, например — это тоже VPN.


        1. a_andreev Автор
          04.05.2018 08:28

          Привет
          VPN-ам такого рода и сигналинг не нужен )
          для PPPoE, к слову, даже IP не нужен, потому-что oE