Скорость развития технологий в наши дни поражает. Скачок научно-технического прогресса в последние годы можно сравнить разве что с темпами развития космической отрасли в период с конца 50-х по середину 70-х годов ХХ века. Как тогда присутствие человека в космосе стало реальностью, так же и сейчас повсеместная замена людей машинами уже не кажется чем-то заоблачным.
Автоматизация процессов стала полноценным «трендом» нашего времени и продолжает расширять свое влияние практически во всех сферах деятельности: начиная с сельского хозяйства и заканчивая «умными домами» или искусственным интеллектом.
Данная тенденция диктует свои правила игры и в сфере бизнеса. Игроки рынка, недостаточно инвестирующие в оптимизацию своих бизнес- и производственных процессов, в их удешевление и ускорение путем автоматизации, совсем скоро окажутся «за бортом».
Автоматизация производства – это, если хотите, уже гигиена. И речь здесь идет как о производстве автомобилей (с заменой ручной сборки конвейером), так и о производстве программного обеспечения (с заменой ручного тестирования автотестами) или предоставлении услуг связи (с заменой ручного труда телефонисток сначала на коммутационное оборудование, а затем и вовсе на новые технологии связи).
Понимая значимость автоматизации в современных реалиях, мы в компании Huawei считаем важным и полезным делиться нашим опытом автоматизации работы сетевого оборудования Huawei c пользователями нашей продукции и представляем вашему вниманию цикл постов, посвященных автоматизации.
В «первой серии» пойдет речь о средствах управления событиями, реализованных в оборудовании Huawei.
Управление событиями
Любой сетевой протокол содержит в себе описание реакции на определенные события. Например, при падении интерфейса, включенного в процесс OSPF или RIP, маршрутизатор, обнаруживший это событие, рассылает уведомления своим соседям. Но множество реакций на события, реализованное в любом стандартном протоколе, не исчерпывает всего многообразия задач, с которыми может столкнуться специалист, обслуживающий систему. Рассмотрим пример:
В данном случае, как мы видим, по каждому внешнему каналу передаются как данные, так и голос: настроен QoS, выделяющий по 50% пропускной способности каждого канала для голосового трафика. Но при падении одного из каналов необходимо изменить настройки оставшегося таким образом, чтобы приоритетная очередь для голосового трафика занимала не 50%, а 70%.
Эту задачу, безусловно, можно решить с использованием NMS- или SDN-контроллера (т.е. внешней управляющей системы). Также, вероятно, можно решить ее и стандартными средствами на маршрутизаторе, но данное решение весьма неочевидно.
Особенно в более сложной (как и наша жизнь :) ) ситуации, где скорости каналов различны и принимаются они на разные маршрутизаторы:
Вот здесь-то и приходит «звездный час» системы управления событиями, которая была внедрена нашей компанией в сетевую OS Huawei.
Система управления событиями OPS (Open Programming System) позволяет автоматически выполнять определенный набор действий (изменять конфигурацию, сохранять файл или что-либо еще) при возникновении некоторых событий.
Перейдем к деталям.
Оборудование, поддерживающее OPS
Система OPS в настоящий момент поддерживается на коммутаторах серии CE и на маршрутизаторах серий NE40 и AR. У каждой из этих моделей есть свои особенности поддержки OPS, которых мы обязательно коснемся.
Программирование
Правила реакции на события в общем случае можно создавать с использованием CLI (командной строки) или же писать на Python (языке программирования). Для описания самих правил можно также использовать как CLI, так и Python. При этом на коммутаторах CE можно использовать оба инструмента, на маршрутизаторах AR – только Python, а на маршрутизаторах NE40 – только командную строку.
Давайте взглянем на пример описания события и реакции на него при помощи CLI.
Пример #1:
[~CE]ops
[~CE-ops]assistant test1
[*CE-ops-assistant-test1]condition syslog pattern “.*Loopback[3-9].*own”
[*CE-ops-assistant-test1]execute 1.1 command sys
[*CE-ops-assistant-test1]execute 1.2 command int gi0/0/0
[*CE-ops-assistant-test1]execute 1.3 command undo shutdown
[*CE-ops-assistant-test1]execute 1.4 command commit
[*CE-ops-assistant-test1]commit
//Расшифруем: если какой-либо из интерфейсов Loopback3, Loopback 41 и т.д. перейдет в состояние down, то интерфейс Gigabit Ethernet 0/0/0 выйдет из состояния administratively down.
В другом примере описание события задано при помощи CLI, а реакция описана на Python (в файле backupconfig.py).
Пример #2:
[~CE-ops] assistant Name /* create assistant */
[*CE-ops-assistant-Name] condition event feature configuration name cfg_file_change
[*SwitchA-ops-assistant-backup_config] execute 1 python backupconfig.py
[*SwitchA-ops-assistant-backup_config] commit
//Расшифруем: при изменении файла конфигурации запускается скрипт backupconfig.
Типы событий
С помощью CLI можно настраивать реакцию на следующие типы событий:
- запись в syslog;
- запись в trapbuffer;
- посылку или прием SNMP;
- возникновение аварии (alarm);
- значение таймера.
Благодаря Python можно дополнительно отследить:
- ввод команды;
- изменение маршрута;
- изменение состояния интерфейса;
- результат работы NQA (SLA).
Реакция на события
Наиболее часто используемым является изменение конфигурации устройства. В самом простом случае при помощи CLI задается последовательность команд (как показано в примере #1), второй параметр определяет последовательность исполнения (в алфавитно-цифровом порядке).
В первом примере последовательно вводятся команды:
- Вход в режим конфигурирования (system-view);
- Вход в подрежим конфигурирования интерфейса Gigabit 0/0/0;
- Поднятие интерфейса;
- Сохранение изменений.
OPS позволяет указать до 10 последовательных команд. Если же необходимо выполнить больше 10 команд, можно воспользоваться bat-файлом и запустить его при помощи команды execute 1 batch-file myfile.bat
Очевидно, с использованием Python можно применять более сложные конструкции, чем линейное выполнение последовательных команд конфигурирования.
Помимо простого выполнения команд, OPS позволяет:
- сохранять и анализировать сохраненные переменные;
- высылать уведомления на syslog-сервер;
- уведомлять пользователя, принимать от него и анализировать ввод с терминала;
- выполнять вход на удаленные устройства по протоколам telnet, ssh и др. и выполнять там какие-либо действия.
Немного магии на «Питоне» :)
Рассмотрим пример простой оболочки (shell).
- Напишем на Python простейшую оболочку, которая будет считывать ввод пользователя и выполнять команды Python. На этом примере рассмотрим функции ввода/вывода на терминал, исполнение команд и анализ результата исполнения.
import os,sys def run(c): b=c.split() if b[0]== «ls»: if b.__len__() == 1: b.append(«/»); res = os.listdir(b[1]) print print(res) else: try: exec(c) return 1 except: print(«Something wrong») return 0 def get_command(ops,VTY): a, b = ops.terminal.write(«>>>«,vty = VTY) a, b = ops.terminal.read(maxLen = 200,timeout = 60,vty=VTY) if a==None: a='q'; return a #Ставим ловушку на ввод команды sh def ops_condition (ops): value, err_str = ops.cli.subscribe(«cli1», «^sh$», enter=True, sync=True, sync_wait=500) return 0 # Собственно, сам shell def ops_execute (ops): key, value = ops.environment.get(«_cli_vty») while 2<3: command=get_command(ops,key) if command == «q»: break; if command <> '': run(command) continue print print(«exit\n») return 0
В данном случае мы видим четыре функции:
- run(command) – выполняет команду command, возвращаемое значение не анализируется;
- get_command(OPS,VTY) – на вход подается встроенный объект OPS и номер терминала, возвращает введенную команду;
- «ловушку» ops_condition(OPS);
- исполнение ops_execute(OPS) – в случае срабатывания «ловушки».
Функции ops_condition(OPS) и ops_execute(OPS) являются обязательными.
run(command)
Функция исполняет команду Python. Если введена команда ls, которой в Python нет, то она заменяется на имеющуюся в Python команду os.listdir(path).
Если рath не указан, делается подстановка path=”/”. Таким образом можно создавать различные alias, т.е. аббревиатуры, которые будут заменяться на развернутые выражения.
get_command(OPS,VTY)
Считывает и возвращает пользовательский ввод. Если ввода не было в течение 60 секунд, возвращает значение окончания “q” – quit.
ops_condition(OPS)
«Ловушка». В нашем случае ставим триггер на ввод команды sh:
ops.cli.subscribe(«cli1», «^sh$», enter=True, sync=True, sync_wait=500)
“cli1” – имя «ловушки», нужно для обрабатывания комплексного события, состоящего из нескольких «ловушек»;
^sh$ – regexp, описывающий строку, состоящую только из двух символов, а именно
sh. Например, под regexp ^sh попала бы и команда shutdown, а под sh – traffic-shaping.
ops_execute(OPS)
Сначала считывает имя терминала в переменную key, а затем в бесконечном цикле (с помощью незамысловатого while 2<3) считывает ввод пользователя в переменную command и исполняет его (run(command)) или прекращает исполнение if command==”q”.
- Собственно, посмотрим, что получилось. Ввод пользователя будет обозначен болдом, а ответ от маршрутизатора – синим цветом.
Скрипт должен быть скопирован на flash маршрутизатора, а затем его необходимо скомпилировать:
<AR2>ops install file <scriptname.py>
Затем необходимо создать assistant:
<AR2>system-view
Enter system view, return user view with Ctrl+Z.
[AR2]ops
[AR2-ops]script-assistant python <scriptname.py>
Проверяем:
<AR2>sh
>>>
Получили приглашение ввода. Тут нужно заметить, что контекст исполнения в Python сохраняется только внутри exec(). Мы не использовали форму exec(code,global,local), поэтому присвоение значения переменной и его печать задаем в одной строке, то есть внутри одного и того же exec.
>>>a=2+3; print(«\n\r»);print a
5
Конечно, можно было воспользоваться следующей формой:
>>>global a
>>>globals()[«a»]=2+3
>>>print a
Результат был бы тот же, и на самом деле это не имеет значения, поскольку в реальных программах мы не будем использовать exec почти никогда, а этот простенький shell написан лишь для изучения возможностей Python на маршрутизаторах Huawei.
Работа с файлами
>>>a=dir(os);print a['EX_CANTCREAT', …'O_APPEND', 'O_ASYNC', 'O_CREAT', …, 'O_RDWR', …]
Как видите, здесь присутствуют константы для создания файлов, и теперь можно попробовать создать файл.
Работать будем с каталогом sd1: или /mnt/sd1 – это одно и то же.
>>>ls /mnt/sd1
['AR2220-V200R003C01SPC900.cc',…, 'V600R008C10SPC300_RM.mod', 'AR2220-V200R007C00SPC600.cc', 'shelldir', 'python', …]– видим, что в выводе присутствуют файлы из sd1:. Сравним:
<AR2> dir
Directory of sd1:/ Idx Attr Size(Byte) Date Time(LMT) FileName 0 -rw- 94,689,536 Apr 23 2014 17:38:30 AR2220-V200R003C01SPC900.cc … 22 -rw- 1,113,612 Dec 16 2015 14:38:44 V600R008C10SPC300_RM.mod 23 -rw- 123,975,040 Mar 15 2016 13:18:56 AR2220-V200R007C00SPC600.cc 24 drw- - Apr 01 2016 10:16:28 shelldir 25 drw- - Apr 06 2016 14:45:26 python
Самый простой способ создать файл:
>>>os.system('echo qqq > /mnt/sd1/s.s')
<AR2> dir s*
Directory of sd1:/ Idx Attr Size(Byte) Date Time(LMT) FileName 9 -rw- 4 Oct 09 2016 17:24:15 s.s 1,961,192 KB total available (1,402,760 KB free)<AR2>more s.s
Qqq
Собственно, вот и наш файл. Теперь возникает вопрос: как читать из него? В данном случае мы воспользуемся теми средствами, которые предоставляет модуль os.
>>>f=os.open('/mnt/sd1/s.s',os.O_CREAT|os.O_RDWR);print f
47
>>>str=’222';os.write(47,str)
>>>str='\n\r111';os.write(47,str)
>>>os.close(47)
<AR2>more s.s
222
111
К слову, аналогичного результата можно добиться, используя os.system:
>>>os.system('echo 222 > /mnt/sd1/d.d')
>>>os.system('echo 111 >> /mnt/sd1/d.d')
<AR2>more d.d
222
111
Теперь прочитаем из файла:
>>>f=os.open('/mnt/sd1/s.s',os.O_RDONLY);print f
49
>>>s=os.read(49,8);print s
222
111
Как видите, работать с файлами в оболочке OPS достаточно просто, при этом можно писать довольно сложные обработчики событий, используя описанный функционал.
Функционал, который обеспечивает OPS, позволяет решать те же задачи, которые решает аналогичная система управления событиями EEM (Embedded Event Management) от Cisco Systems, за исключением отправки e-mail-сообщений.
Но, во-первых, это лишь первая реализация Python, и в следующих версиях ожидается дополнительный функционал. Во-вторых, e-mail все-таки можно оправить через open relay, используя telnet на порт 25. Функции для остальных обработчиков и выполнения действий полностью идентичны Cisco EEM.
Конец первой серии
Мы попытались продемонстрировать некоторые возможности применения CLI и Python для управления событиями на маршрутизаторах Huawei. Насколько это оказалось полезным, судить вам. Будем рады увидеть ваши комментарии, вопросы и отзывы.
Мы же, со своей стороны, продолжим делиться нашими наработками в области автоматизации работы коммуникационного оборудования.
А пока завершим первую серию словами юного Сережи Сыроежкина, героя фильма «Приключения Электроника»:
До чего дошел прогресс: труд физический исчез,
Да и умственный заменит механический процесс.
Музыка, титры, to be continued…
Комментарии (17)
immaculate
03.11.2016 21:20Какой-то у вас Python очень непитонистый. Одно только «b.__len__()» вместо «len(b)» чего стоит… Это намеренное запутывание кода что-ли?
JIghtuse
04.11.2016 09:27+1Да, будто специально в четыре десятка строк ошибки вталкивали. Чего стоит одно форматирование — кругом
«»
вместо""
. Код уже не скопипастить, надо ещё каким-нибудь sed-ом пройтись.
Какая-то свежая конструкция:if command <> '':
Просто классика:if a==None:
Вот это тоже мило:
print print(res)
Это какая версия Python вообще? =) для второй лишние скобки, для третьей наоборот отсутствуют.
WAT?
while 2<3:
Словом, есть и получше уроки.immaculate
04.11.2016 11:31+1Еще перл:
a, b = ops.terminal.write(«>>>«,vty = VTY) a, b = ops.terminal.read(maxLen = 200,timeout = 60,vty=VTY) if a==None: a='q'; return a
Первый раз значения присваиваются, но не используются. Зачем? Все это проще записать так:
ops.terminal.write(">>>", vty=VTY) a, _ = ops.terminal.read(maxLen=200, timeout=60, vty=VTY) return a if a else 'q'
Ребята! Если у вас внутри такой код, то я никакую вашу продукцию не куплю!
Это явно писал человек, не удосужившийся прочитать даже Python Tutorial (и точно не читавший PEP-8).mumbaI
07.11.2016 11:38+1Я действительно не гуру питона, и эта статься не урок питона на оборудовании Huawei. Статья прежде всего показывает как писать скрипты на оборудовании Huawei и как писать в файл.
Она ориентирована не на программистов, а на сетевых администраторов.
Много вы встречали документации на TCL и, прежде всего, его API, когда его только встроили в Cisco?
Тот факт, что переменные не анализируются, говорит о том, что из кода убраны проверки, для уменьшения размера кода. Про конструкцию «a, _» согласен, нужно было бы заменить.immaculate
07.11.2016 14:06+1Я думаю, что документация по TCL существовала отдельно от оборудования Cisco и была очень хорошей.
Тут дело в том, что совершенно очевидно, что человек, писавший код, даже не прочитал Python Tutorial. То есть, просто взял скопипастил вместе какие-то куски кода, нарушая все писаные и неписанные стандарты кода на Python. Такого кода в мире очень много, я это знаю, так как регулярно с ним встречаюсь всю жизнь. И эти костыли кое-как в идеальных условиях иногда даже работают.
Но данный подход всегда приводит к проблемам. Этот отвратительный код всегда невозможно поддерживать. Вы в скрипте из 5-10 строчек наделали ошибок практически в каждой строчке. Когда ваш код вырастет до сотен и тысяч строк, это будет прост клубок костылей, скрепленный резинками и скотчем. И малейшие изменения или исправления будут выливаться в огромную головную боль.
Да, хорошо сказать: «этот код не вызывает синтаксических ошибок интерпретатора и вроде как работает, я умываю руки». Но такой подход никогда не бывает масштабируемым.
И вообще, неужели так сложно час потратить на чтение хотя бы Python Tutorial, входящего в состав любого дистрибутива Python? Быстрее надо лес рубить, некогда топор точить?mumbaI
09.11.2016 07:08Вы пишете, что «Вы в скрипте из 5-10 строчек наделали ошибок практически в каждой строчке.» что вы подразумеваете под ошибками?
mumbaI
05.11.2016 22:23Это не урок по питону. Это дополнение к документации, в которой не все можно найти, прежде всего очень сильно хромает та часть, где описан API работы с файлами.
Статья сильно отредактирована, видимо в процессе редактирования у кого-то редактор заменил кавычки '' на «», так же убраны мои извинения за корявость кода и прежде всего за конструкцию while 2<3:, ну а конструкция if <результат ввода>==None: мне кажется более читаемой, чем просто if <результат ввода>
Тем более, что если рассмотреть задачу, которая стоит перед сетевым администратором, изображенную на втором рисунке, то станет понятно, что выигрыш в милисекунду (что очень много для кода) — ничто для сетевого протокола.
Используется же питон 2.7
YaakovTooth
04.11.2016 18:23+1Ребят, очень и очень плохой код, стиль и вообще всё. От ==None: до Except:, который перехватит вообще всё, включая RuntimeError и KeyboardInterrupt.
mumbaI
05.11.2016 22:44-1Насчет стиля я уже ответил, а вот по-поводу Except — и пусть перехватывает. Гораздо лучше, если при наборе
f=os.open('/mnt/sd1/s.s',os.O_CREAT|os.O_RDWR);print f
в случае, если ты ошибешься и assistant не вылетит, и его не нужно будет переустанавливать.
Это во-первых «программа-зубочистка», то есть одноразовая, во-вторых ее цель показать использование средств os в качестве API.
Хотелось бы обратить внимание на то, что питон работает на маршрутизаторе. на домашней мыльнице, например или на чем-то более крупном. Вы серьезно считаете, что кривой код прикладной программы, влияет на возможности самой технологии?
usachev_1991
07.11.2016 11:39+1It is not pythonic way.
Ну хотя бы так:
import os import sys # не стоит сливать импорты def run(c): b = c.split() if b[0] == 'ls': if len(b) == 1: # if b.__len__() == 1: b.append('/'); res = os.listdir(b[1]) print(res) else: try: exec(c) # выполнять любой код? return 1 # зачем возвращаемое значение? except: print('Something wrong') return 0 # зачем возвращаемое значение? # неявный return None (return 0) def get_command(ops, VTY): ops.terminal.write('>>>',vty = VTY) # a, b = ... - лишнее a, _ = ops.terminal.read(maxLen = 200,timeout = 60,vty=VTY) # _ - тоже переменная # if a == None: # a = 'q'; return a or 'q' # a or 'q' <=> если a == None, то будет возвращено 'q' def ops_condition(ops): '''Ставим ловушку на ввод команды sh''' # value, err_str - лишние ops.cli.subscribe('cli1', '^sh$', enter=True, sync=True, sync_wait=500) return 0 # зачем? def ops_execute (ops): '''Собственно, сам shell''' key, _ = ops.environment.get('_cli_vty') # value не использовалась while True: # 2<3 ??? command = get_command(ops, key) if command == 'q': break # не нужно ';' if command: # не нужно писать if command != '': run(command) continue print('\nexit\n') return 0 # зачем?
Лучше обернуть код в класс.
mumbaI
09.11.2016 07:27спасибо за конструктивную критику. отвечу на некоторые вопросы
while True: vs while 2<3
конечно я сначала использовал именно эту конструкцию, но интерпретатор ругался на ошибку в строке. после нескольких попыток я плюнул и использовал более корявый, но работающий механизм. Это не является оправданием кривости кода. Но у меня была задача заставить маршрутизатор писать в файл. документация по API по этому поводу ничего не говорила, поэтому я собрал этот код на коленке исключительно для выяснения того, какие системные модули используются и можно ли писать в файл и читать из него. Да, разумеется, о питоне до этого я ничего не знал, как, впрочем и сейчас.
зачем return 0 в ops_execute и ops_condition. Это, конечно, копипаста из документации по API.
зачем return 0 в других функциях — затрудняюсь ответить.usachev_1991
09.11.2016 08:25while True: vs while 2<3: — Это странно, вероятно, ошибка была в другом. Можно и
while 1
.
return — В месте вызова функции
run
возвращаемое значение не ожидается,return
— лишнее.
Функция может заканчиваться инструкциейreturn
или не иметь её (при этом вернетNone
). Следующие функции эквивалентны, все вернутNone
:
def foo(): return None def foo(): return def foo(): pass # ничего не выполняет
В последнем случае инструкция
pass
ничего не выполняет, просто синтаксис требует тело функции, поэтому тут только неявныйreturn None
.
В функции
run
3 сценария:
a) условие истинно, тогда вернётся None;
b) условие ложно, нет исключения, тогда вернётся 1;
c) условие ложно, есть исключение, тогда вернётся 0.
Если возвращаемое значение рассматривать как булево, то как-то нелогично, т.к. только b) дастTrue
.
satandyh
Будет ли что-то с применением Ansible?