Хабрахабр

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

Использование программирования в сетевом деле уже стало трендом, поэтому в продолжении статьи Зачем сетевым инженерам программирование я начинаю серию небольших заметок про автоматизацию решения тех или иных практических задач. Чтобы развеять ореол сложности вокруг этой темы, будут опубликованы некоторые примеры и кейсы, в основном с использованием Python, и даны ссылки на более глубокий материал и техническую документацию. Вступительная статья этого цикла ниже.
Сначала пара слов для антагонистов, не спешите говорить «это не для меня». Тенденции, происходящие в индустрии сетей передачи данных, отодвигают вопрос «как сделать» на второй план, перемещая на первый план вопрос эффективной, т.е. безошибочной и не затратной, эксплуатации, эти же тенденции толкают нас, сетевых инженеров, к изучению различных средств автоматизации. Тем, чей разум не «заражен» вирусом под названием указатель на указатель на функцию, я предлагаю начать этот путь с Python, хорошим подспорьем в этом деле может послужить книга Automate the Boring Stuff with Python. Книга написана в очень дружественной для новичков форме, и прекрасно подойдет для получения необходимого минимума знаний и практического опыта. Материал глав, примеры и задания соответствуют духу и философии python:

  • Красивое лучше, чем уродливое.
  • Простое лучше, чем сложное.
  • Сложное лучше, чем запутанное.
  • Практичность важнее безупречности.

Прочитав меньше половины глав, вы поймете структуры данных python и сможете писать первый код, а прочитав вторую половину вы получите представление о возможностях python в решении прикладных задач, наподобие отправки HTTP запросов, работы с данными в формате CSV или разборе документов JSON. Я сторонник изучения программирования по схеме от низкоуровневого к высокоуровневому, однако ознакомившись с книгой, я заметил, что повествование само собой подталкивает к понимаю азов программирования с такой же простотой и естественностью, с которой дети начинают общаться на родном языке. Если вы не являетесь профессиональным программистом, а просто подыскиваете литературу чтобы научится формулировать свои мысли в виде кода, попробуйте сделать python своим родным языком.

В качестве опорной я выбрал задачу проверки наличия на удаленной стороне ответного плеча для сконфигурированных RSVP LSP. Итак, первый пример посвящен проверке operation состояний маршрутизатора Juniper Networks. Сервисная сигнализация вполне может работать по IP, а data-plane трафику необходимы бесшовные MPLS пути между PE маршрутизаторами. Наличие двустороннего MPLS транспорта является обязательным условием для передачи трафика различного рода VPN. Для проверки 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) позволяют абстрагироваться от тонкостей этого формата.

Имя метода находится прямо внутри тегов , в данном случае это get-mpls-lsp-information Каждой команде Junos соответствует некоторый метод Pyez, чтобы узнать имя этого метода используйте опцию | display xml rpc.

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

Заполнение формата представлений и таблиц в коде примера осуществляется в строковой переменной yml, ключевые элементы этого формата ниже:

Значение rpc используется в таблице, и содержит имя метода из display xml rpc вывода.

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

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть