TextFSM - Изменение стейта

Что?

TextFSM это питоновский модуль для парсинга (плохо) форматированного текста. Изначально был спроектирован для парсинга вывода cli разных устройств - в частности, сетевых.

В TextFSM существует три важных сущности: ввод, список правил и вывод.

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

Как?

TextFSM это final state machine - машина конечных автоматов. В простейшем случае, в TextFSM есть два состояния: Start (обязательно должно присутствовать в файле шаблона) и EOF - implicit (подразумеваемое) состояние, переход на которое происходит когда мы прочитали весь файл.

Алгоритм FSM примерно такой:

  1. FSM читает строчку из ввода;
  2. FSM проверяет, матчится ли эта строчка с правилами из списка правил. Список правил для каждой строчки из ввода проверяется сверху вниз, с первой строки списка правил;
  3. Если строчка матчится одной из строчек списка правил, FSM выполняет действие и переходит к следующей строчке ввода, снова ее сверяя со списком правил, начиная с первой строчки списка. Также, в этом шаге могут заполняться значения переменных.

Подробнее про алгоритм FSM, действия и правила можно почитать в документации (см. Ссылки)

Зачем?

Если у нас простой вывод с повторяющимися значенями, то можно использовать один стейт, наполняя строчку значениями отматченных переменных. Если вывод менее структурирован, то, вероятнее всего, придется использовать дополнительные стейты.

FSM с одним состоянием (не считая EOF)

Одного стейта достаточно, когда ввод однозначно определяет разделитель между строчками вывода, как, например, тут:

[ Ввод ]

Server group radius
    Sharecount = 1  sg_unconfigured = FALSE
    Type = standard  Memlocks = 1
    Server(10.14.121.20:1814,1815) Transactions:
    Authen: 6	Author: 0	Acct: 0
    Server_auto_test_enabled: FALSE
     Keywrap enabled: FALSE
    Server(10.3.121.20:1814,1815) Transactions:
    Authen: 3	Author: 33	Acct: 333
    Server_auto_test_enabled: FALSE
     Keywrap enabled: FALSE
    Server(10.4.12.26:1814,1815) Transactions:
    Authen: 4	Author: 0	Acct: 0
    Server_auto_test_enabled: FALSE
     Keywrap enabled: FALSE
    Server(10.129.111.20:1814,1815) Transactions:
    Authen: 5	Author: 0	Acct: 555
    Server_auto_test_enabled: FALSE
     Keywrap enabled: FALSE
    Server(172.30.2.123:1812,1813) Transactions:
    Authen: 0	Author: 0	Acct: 0
    Server_auto_test_enabled: TRUE
     Keywrap enabled: FALSE
Server group SERVER_GROUP_2
    Sharecount = 1  sg_unconfigured = FALSE
    Type = standard  Memlocks = 1
    Server(172.30.10.123:1812,1813) Transactions:
    Authen: 2249	Author: 0	Acct: 22424
    Server_auto_test_enabled: TRUE
     Keywrap enabled: FALSE

Эту конструкцию можно распарсить так:

[ Список правил ]

Value Filldown NAME (\S+)
Value Filldown SHARECOUNT (\d+)
Value Filldown SG_UNCONFIGURED (TRUE|FALSE)
Value Filldown TYPE (\w+)
Value Filldown MEMLOCKS (\d+)
Value Required SERVER_IP (\d+\.\d+\.\d+\.\d+)
Value AUTH_PORT (\d+)
Value ACC_PORT (\d+)
Value AUTO_TEST_ENABLED (TRUE|FALSE)
Value KEYWRAP_ENABLED (TRUE|FALSE)
Value AUTHEN_COUNT (\d+)
Value AUTHOR_COUNT (\d+)
Value ACCT_COUNT (\d+)

Start
  ^Server group ${NAME}
  ^.*Sharecount = ${SHARECOUNT}.*sg_unconfigured = ${SG_UNCONFIGURED}
  ^.*Type = ${TYPE}.*Memlocks = ${MEMLOCKS}
  ^.*Server\(${SERVER_IP}\:${AUTH_PORT},${ACC_PORT}\)
  ^.*Authen: ${AUTHEN_COUNT}.*Author: ${AUTHOR_COUNT}.*Acct: ${ACCT_COUNT}
  ^.*Server_auto_test_enabled: ${AUTO_TEST_ENABLED}
  ^.*Keywrap enabled: ${KEYWRAP_ENABLED} -> Record
graph TD;
  Start-->EOF;

[ Вывод ]

['NAME', 'SHARECOUNT', 'SG_UNCONFIGURED', 'TYPE', 'MEMLOCKS', 'SERVER_IP', 'AUTH_PORT', 'ACC_PORT', 'AUTO_TEST_ENABLED', 'KEYWRAP_ENABLED', 'AUTHEN_COUNT', 'AUTHOR_COUNT', 'ACCT_COUNT']
['radius', '1', 'FALSE', 'standard', '1', '10.14.121.20', '1814', '1815', 'FALSE', 'FALSE', '6', '0', '0']
['radius', '1', 'FALSE', 'standard', '1', '10.3.121.20', '1814', '1815', 'FALSE', 'FALSE', '3', '33', '333']
['radius', '1', 'FALSE', 'standard', '1', '10.4.12.26', '1814', '1815', 'FALSE', 'FALSE', '4', '0', '0']
['radius', '1', 'FALSE', 'standard', '1', '10.129.111.20', '1814', '1815', 'FALSE', 'FALSE', '5', '0', '555']
['radius', '1', 'FALSE', 'standard', '1', '172.30.2.123', '1812', '1813', 'TRUE', 'FALSE', '0', '0', '0']
['SERVER_GROUP_2', '1', 'FALSE', 'standard', '1', '172.30.10.123', '1812', '1813', 'TRUE', 'FALSE', '2249', '0', '22424']

Обратите внимание на строчку Keywrap enabled:. Она идет в конце каждой секции и на ней отрабатывает действие Record - которое записывает накопленные значения в строчку вывода.

FSM с двумя состояниями (не считая EOF)

[ Ввод ]

total number of groups:3

following RADIUS server groups are configured:
        group radius:
                server: all configured radius servers
                deadtime is 0
        group RADIUS_GROUP_A:
                server: 10.65.101.22 on auth-port 1814, acct-port 1815
                server: 10.110.101.22 on auth-port 1814, acct-port 1815
                server: 10.194.101.22 on auth-port 1814, acct-port 1815
                server: 10.10.10.27 on auth-port 1814, acct-port 1815
                deadtime is 0
                vrf is management
                Source interface mgmt0
        group RADIUS_GROUP_B:
                server: 172.40.2.14 on auth-port 1812, acct-port 1813
                deadtime is 10
                Source interface loopback0

[ Список правил ]

Value Required NAME (\S+)
Value List SERVER (\d+\.\d+\.\d+\.\d+|all configured radius servers)
Value DEADTIME (\d+)
Value List AUTH_PORT (\d+)
Value List ACCT_PORT (\d+)
Value VRF (\S+)
Value SOURCE_INTERFACE (\S+)

Start
  ^.*group ${NAME}: -> Group

Group
  ^.*server:\s*${SERVER}\s*on auth-port ${AUTH_PORT}, acct-port ${ACCT_PORT}
  ^.*server:\s*${SERVER} -> AllServers
  ^.*vrf is ${VRF}
  ^.*Source interface ${SOURCE_INTERFACE} -> Record Start
  ^.*deadtime is ${DEADTIME}

AllServers
  ^.*deadtime is ${DEADTIME} -> Record Start
graph TD;
  Start-->AllServers;
  AllServers-->Start;
  Start-->EOF;

[ Вывод ]

['NAME', 'SERVER', 'DEADTIME', 'AUTH_PORT', 'ACCT_PORT', 'VRF', 'SOURCE_INTERFACE']
['radius', ['all configured radius servers'], '0', [], [], '', '']
['RADIUS_GROUP_A', ['10.65.101.22', '10.110.101.22', '10.194.101.22', '10.10.10.27'], '0', ['1814', '1814', '1814', '1814'], ['1815', '1815', '1815', '1815'], 'management', 'mgmt0']
['RADIUS_GROUP_B', ['172.40.2.14'], '10', ['1812'], ['1813'], '', 'loopback0']

Ага, но почему так хитро? Потому что в группе radius последняя строчка перед следющей группой это deadtime is 0, а в остальных группах последняя строчка это Source interface. Для обработки этих разных групп нужны разные списки правил, которые раскидываются по разным стейтам (Start и AllServers).

Условие перехода на стейт AllServers - строчка server: all configured radius servers, которая матчится правилом ^.*server:\s*${SERVER} -> AllServers.

В стейте AllServers мы доходим до строчки deadtime is, записываем состояние и возвращаемся в стейт Start.

Если FSM остается в стейте Start, запись состояния происходит после матчинга строчки Source interface.

Ссылки

Следующий
Предыдущий