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

    Использование программирования в сетевом деле уже стало трендом, поэтому в продолжении статьи Зачем сетевым инженерам программирование я начинаю серию небольших заметок про автоматизацию решения тех или иных практических задач. Чтобы развеять ореол сложности вокруг этой темы, будут опубликованы некоторые примеры и кейсы, в основном с использованием 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 ''
    
    • +15
    • 11,2k
    • 4
    Поделиться публикацией
    Комментарии 4
      0
      Наличие двустороннего MPLS транспорта является обязательным условием для передачи трафика различного рода VPN


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

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

      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

      Самое читаемое