Child pages
  • Валидация конфигурации или "Факты, только факты и ничего кроме фактов"

Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

  • interface
  • subinterface
  • service
  • ntpserver
  • staticroute
  • sysloghost
  • system
  • user
  • vlan
  • vrf
Code Block
languagepy
titleСборник фактов из interafces.py
collapsetrue
def __init__(self, name, description=None, admin_status=False, 
             speed="auto", duplex="auto", protocols=None,
             profile=None, type=None, mac=None, default_name=None,
             aggregated_interface=None,
             **kwargs):
    super(Interface, self).__init__()
    self.name = name
    self.description = description
    self.admin_status = admin_status
    self.has_description = False
    self.speed = speed
    self.duplex = duplex
    self.protocols = protocols
    self.profile = profile
    self.type = type
    self.mac = mac
    self.default_name = default_name
    self.aggregated_interface = aggregated_interface

 

Наш парсер

И так, как же нам написать свой парсер конфигурации и получить многие знания, имея доступ только к конфигурации оборудования? Сами по себе парсеры лежат в папке cm/parsers. Структура подпапок напоминает структуру профилей оборудования в sa/profiles. Парсеры в НОКе можно, условно, поделить на 2 типа: использующие библиотеку pyparsing и самописные (не использующие эту библиотеку). Попробуем показать оба подхода, на примере создания парсера для Huawei.

...

Note

Несколько важных особенностей pyparsing

  • По умолчанию, пробелы служат разделителями токенов, и, поэтому нет необходимости выделять их специальным образом

Поезности по поводу работы pyparsing

 

Работа с pyparsing чем-то напоминает работу с регулярными выражениями, только здесь проще синтаксис и понятнее что мы делаем. В нашем случае основным понятием для парсинга будут токены (TOKENS). Токены могут состоять из различных элементов и объединяться с друг другом. Для удобства уже присутствуюет некоторый набор токенов, его можно найти в файле . Возьмём кусок конфига Huawei и на его примере познакомимся с терминологией:

Image Added

Здесь выделены:

  • Токены (tokens) - коричневый цвет. Формально, токены - это некоторые структурные единицы, из которых состоит выражение н-р, строку ntp-service unicast-server 172.27.125.9 можно разибить на токены в следующих вариантах (разный цвет - разный токен):
    • ntp-service unicast-server 172.27.125.9
    • ntp-service unicast-server 172.27.125.9
    • и т.д
  • Строки (line) - выделены зелёным. Начинаются и заканчиваются разделителем строки \n или начинаются с начала строки (символ ^ в регулряных выражениях) а заканчиваются концом строки (символ # в регулярных варежениях). Для работы с ними используется операторы StartLine() и EndLine()
  • Выражения. Условное определение для набора токенов. Сами по себе токены могут состоять из других токенов. Для простоты будем называть это выражением (или фразой).
  • Блоки (block) - выделены оранжевым. Состоят из некоторого количества выражений. Н-р. блок, описывающий интерфейсы, или aaa.
Tip

Можно сделать вывод, чты pyparser подходит для уже оформленного и структурированного текста. Парсить им неотформатированный текст может быть не простой задачей. Для такого случая может подойти один из вариантов, описанный в главе Handmade


В нашем случае текст структурирован за счёт выделения блоков отступами и разделения их знаком решётки #. Чем мы и воспользуемся.

Tip

Наиболее употребительные токены уже объедины в выражения и доступны для использования (импорта) из cm/parsers/tokens.py.

...

Например:

  • SPACE = Suppress(Word(" ").leaveWhitespace()) - Описывает пробел/s (слово Word(" ").leaveWhitespace()), который/е необходимо пропустить (слово Suppress)
  • INDENT = Suppress(LineEnd() + SPACE) - Описывает конструкцию вида "\n\s+" т.е. перевод строки (LineEnd()) после которого идут пробелы (SPACE)
  • REST = SPACE + restOfLine - ключевое слово restOfLine обозначает до конца строки

С остальными конструкциями можно ознакомиться самостоятельно.

...

 

Для ознакомаления с инструкциями и работой с текстом можно воспользоваться shell'ом. Ниже приведён пример использования конструкций pyparsing для работы с текстом.

Code Block
languagepy
collapsetrue
from pyparsing import *
from noc.cm.parsers.tokens import INDENT, REST

# можно использовать вот так.
# from noc.sa.models.managedobject import *
# m = ManagedObject.objects.get(name='cisco-hostname')
# config_cisco = m.config.read()



# Мы всегда можем положить всю конфигурацию в переменную при помощи тройных кавычек
config_cisco = """service timestamps debug datetime
service timestamps log datetime
service password-encryption
!
hostname C2960_2
!
no logging console
"""
# Для удобства различения токены обозваны большими буквами.
HOSTNAME = LineStart() + Literal("hostname") + REST.copy()
# Метод .searchString производит поиск токена в тексте и выводит найденные списком
HOSTNAME.searchString(config_cisco)
Out[60]: ([(['hostname', 'C2960_2'], {})], {})
 
# В данном примере всё не совсем тривиально. Обращает на себя внмание то, что строка начинается с пробела, это не даёт нам использовать оператор StartLine() т.е. он ожидает сразу после символа переноса строки токен
config_huawei = """!Software Version V100R005C01SPC100
 sysname Huawe23_Stack
#
 vlan batch 2 to 91 95 to 590 592 to 596 600 to 4089
#
 stp instance 0 priority 16384
 stp enable
#
"""
# Используем конструкцию INDENT, т.е. \n\s+ - конец строки и пробелы после
HOSTNAME = INDENT + Literal("sysname") + REST.copy()
HOSTNAME.searchString(config_huawei)
Out[13]: 
([(['sysname', 'Huawe23_Stack'], {})], {})

в переменную config можно положить конфигурацию оборудования и экспериментировать с парсингом.

 

После того как токены извлечены, можно начинать писать их обработку. Для этого применяются небольшие функции-методы, которым на вход поступает токен, а они устанавливают факт. Выглядят они так:

Code Block
themeRDark
languagepy
# В переменно tokens будет список токенов в формате списка, н-р ['sysname', 'Huawe23_Stack'], к ним можно обращаться по порядку
def on_vlan_range(self, tokens):
    # Для каждого из токенов вызываем обработчик.
    for v in ranges_to_list(tokens[0].strip()):
        self.get_vlan_fact(v)

def on_vlan_name(self, tokens):
    self.get_current_vlan().name = tokens[0]

def on_http_server(self, tokens):
    self.get_service_fact("http").enabled = tokens[0] != "undo"

...

Для вызова нужного обработчика мы используем конструкцию .setParseAction(<обработчик>). При совпадениина она передаёт обработчику список токенов. Внутри обработчика мы можем проделать с токенами необходимы нам действия.

Список доступных методов для установки фактов можно посмотреть в других парсерах или в файлике cm/parsers/base.py.

Code Block
languagepy
VLAN_RANGE = LineStart() + Literal("vlan") + Combine(DIGITS + Word("-,") + restOfLine).setParseAction(self.on_vlan_range)
VLAN = LineStart() + Literal("vlan") + DIGITS.copy().setParseAction(self.on_vlan)
VLAN_NAME = Literal("name") + REST.copy().setParseAction(self.on_

...

vlan_name)


HOSTNAME = INDENT + Literal("sysname") + REST.copy().setParseAction(self.on_hostname)

После того, как мы научились извклекать необходимые нам факты

 

Handmade

можем смело скопировать один из готовых парсеров (н-р Cisco.IOS) и доработать его под свои нужды. Не обязательно стремиться извлечь всё что можно, достаточно только нужное для работы.

Берём наши блоки, подменяем в исходном тексте парсера и всё должно и всю конструкцию возвращаем через return. Если всё сделано без ошибок, то можно переходить в раздел Что-то пошло не так.... В нём описан запуск тестирования парсера.

Handmade
Anchor
handmade
handmade

На случай, если наш текст имеет плохо формализованную структуру, в нём сложно выделить блоки и описать его для парсера, ну или просто лень со всем этим возиться... можно воспользоваться более привычными инструментами:

  • Регулярные выражения
  • splitlines(), if - end
  • какой-нибудь ещё парсер...

Все способы связаны с тем, что к нам, фактически, прилетает просто кусок текста и способов, что с ним можно сделать, придумано вагон и маленькая тележека с обозом. Примеры парсеров, организованных таким способом, это DLink.DxS, Juniper.JUNOS.

Подход не отличается, мы ищем нужные нам сведения и вызываем обработчики, передавая им параметры.

Что-то пошло не так....
Anchor
notbad
notbad

Теперь, когда парсер написан, необходимо подключить его к профилю и проверить. Подключение делается путём прописывания конструкции default_parser = "noc.cm.parsers.Huawei.VRP.base.BaseVRPParser" в файл __init__.py соотв. профиля. Для тестирования парсера удобно использоваться shell:

Warning

Необходимо помнить, что при дискавери парсинг фактов происходит только при изменении в конфигурации. Т.е., простой запуск дискавери, не будет приводить к отработке парсеракогда есть изменения в конфигурации.

Code Block
languagepy
import logging
from noc.lib.debug import error_report
from noc.cm.engine import Engine
from noc.sa.models.managedobject import ManagedObject

# Получаем необходимый MO, конфигурацию которого будем парсить
mo = ManagedObject.objects.get(name=MO_NAME)
# Выставляем уровень логгирования на отладку
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
 
engine = Engine(mo)
# Запускаем парсинг конфига
try:
	engine.check()
except:
    print error_report()

На выходе получаем огромную простыню вывода. Если в парсере есть какие-то ошибки - будет трейс. Смотрим и разбираемся что не так

Производим изменения в парсере и перезапускаем discovery чтобы изменения применились.

Code Block
languagebash
./noc ctl restart discovery-default:*

 

Примеры

В папке cm/parsers есть некоторое количество уже написанных парсеров. Рекомендется использовать их, для изучения и как шаблон для написания своего.