TextFSM - Изменение стейта
Что?
TextFSM
это питоновский модуль для парсинга (плохо) форматированного текста.
Изначально был спроектирован для парсинга вывода cli разных устройств - в
частности, сетевых.
В TextFSM
существует три важных сущности: ввод
, список правил
и вывод
.
Вывод cli я буду называть ввод
(хе-хе) - потому что это та информация, которую
нам нужно распарсить. Этот вывод мы скармливаем машине конечных автоматов
TextFSM
, которая обрабатывает (парсит) его в соответствии со списком правил
.
Получившийся текст уходит в вывод
.
Как?
TextFSM
это final state machine - машина конечных автоматов. В простейшем
случае, в TextFSM
есть два состояния: Start
(обязательно должно присутствовать в
файле шаблона) и EOF
- implicit (подразумеваемое) состояние, переход на которое
происходит когда мы прочитали весь файл.
Алгоритм FSM
примерно такой:
FSM
читает строчку изввода
;FSM
проверяет, матчится ли эта строчка с правилами изсписка правил
. Список правил для каждой строчки изввода
проверяется сверху вниз, с первой строки списка правил;- Если строчка матчится одной из строчек
списка правил
, 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
.