Основы автоматизации на SLAX: различия между версиями
(не показано 35 промежуточных версий этого же участника) | |||
Строка 1: | Строка 1: | ||
{{#description2:Основы языка SLAX для автоматизации в JunOS. SLAX Script File Structure. Variables. Простые типы данных. Сложные структуры данных. Специальные переменные. If/then/else. For-each. While, Do-While, For. Материалы для подготовки к экзаменам Juniper Networks}} | |||
=Fundamentals= | =Fundamentals= | ||
- SLAX is based on XPath 1.0 and XSLT 1.0. | - SLAX is based on XPath 1.0 and XSLT 1.0. | ||
Строка 40: | Строка 43: | ||
=SLAX Script File Structure= | =SLAX Script File Structure= | ||
Для просмотра в XML формате используем: | |||
blair> show configuration | display xml | |||
'''Comments:''' - можно располагать хоть где в коде. | '''Comments:''' - можно располагать хоть где в коде. | ||
*(/*) - open-comment | *(/*) - open-comment | ||
Строка 92: | Строка 98: | ||
*'''Strings''': " " или ' '. string() - функция превращает аргумент в строку. | *'''Strings''': " " или ' '. string() - функция превращает аргумент в строку. | ||
*'''Result Tree Fragments''': можем использовать как script output, обработать как строку, или конвертировать в node-set. При объявлении переменной такого типа нельзя заканчивать '';'' после ''}''. '''Каждый шаблон возвращает RTF.''' | *'''Result Tree Fragments''': можем использовать как script output, обработать как строку, или конвертировать в node-set. При объявлении переменной такого типа нельзя заканчивать '';'' после ''}''. '''Каждый шаблон возвращает RTF.''' | ||
*'''Node-set''': =RTF, отличие от RTF - при присвоении переменной значения используем '':=''. XPath expressions работают только с node-sets. | *'''Node-set''': =RTF, отличие от RTF - при присвоении переменной значения используем '':=''. XPath expressions работают только с node-sets. Также отличие - к node-set можно применять операторы сравнения. ( | ) - оператор для создания комбинации node-sets. | ||
Пример: for-each( ($primary-colors | $fav-colors)/color ) {... | |||
==Сложные структуры данных== | ==Сложные структуры данных== | ||
Строка 99: | Строка 106: | ||
Пример задания массива: | Пример задания массива: | ||
var $ | var $interfaces := { | ||
<int> "ge-0/0/0"; | |||
<int> "ge-0/0/1"; | |||
<int> "ge-0/0/2"; | |||
<int> "ge-0/0/3"; | |||
<int> "ge-1/0/1"; | |||
<int> "ge-1/0/2"; | |||
<int> "ge-1/0/3"; | |||
<int> "ge-1/0/4"; } | |||
match / { | |||
<op-script-results> { | |||
<output> "interface 5 = " _ $interfaces/int[5] _ "\n"; | |||
<output> "all interfaces:\n"; | |||
for-each ($interfaces/int) { | |||
<output> .; }}} | |||
Вывод: | |||
blair> op arrays | |||
interface 5 = ge-1/0/1 | |||
all interfaces: | |||
ge-0/0/0 | |||
ge-0/0/1 | |||
ge-0/0/2 | |||
ge-0/0/3 | |||
ge-1/0/1 | |||
ge-1/0/2 | |||
ge-1/0/3 | |||
ge-1/0/4 | |||
Вывод | *хэши - (аналог Dictionary в Python) нет хешей как таковых, но можно использовать node-set XPath expressions особым образом. | ||
Пример: '''hash1''' - где key - status - аттрибут данных (значение элемента int), '''$hash2''' - key - mtu - элемент данных (элемент int, такой же как и int). | |||
var $hash1 := { | |||
<int name = "ge-0/0/0"> "down"; | |||
<int name = "ae2"> "up"; } | |||
var $hash2 := { | |||
<int> { | |||
<name> "ge-0/0/0"; | |||
<mtu> "1500"; } | |||
<int> { | |||
<name> "ae8"; | |||
<mtu> "2000"; }} | |||
match / { | |||
<op-script-results> { | |||
/*Interface status in $hash1*/ | |||
<output> "ge-0/0/0 is " _ $hash1/int[@name == 'ge-0/0/0']; | |||
<output> "ae2 is " _ $hash1/int[@name == 'ae2']; | |||
/*Interface MTU from $hash2*/ | |||
<output> "MTU ae8 = " _ $hash2/int[name=='ae8']/mtu; }} | |||
Вывод: | |||
blair> op hash | |||
ge-0/0/0 is down | |||
ae2 is up | |||
MTU ae8 = 2000 | |||
Еще один метод использования hash - '''XSLT <xsl:key> top-level element''' and '''key()''' function. | |||
key - используются для индексирования node внутри XML. | |||
<xsl:key> top-level element - содержит name, node index, ссылку на нужный node. | |||
key () - функция, которая "достает" нужный node. Содержит имя key и желаемое значение key. | |||
/* Define <xsl:key> elements */ | /* Define <xsl:key> elements */ | ||
<xsl:key name="protocol" match="route-table/rt" use="rt-entry/protocol-name">; | <xsl:key name="protocol" match="route-table/rt" use="rt-entry/protocol-name">; | ||
Строка 155: | Строка 201: | ||
192.168.86.28/30 | 192.168.86.28/30 | ||
192.168.86.44/30 | 192.168.86.44/30 | ||
И последний способ: | |||
var $protocol = 'Static'; | |||
match / { | |||
<op-script-results> { | |||
var $results = jcs:invoke("get-route-information"); | |||
for-each( $results ) { | |||
<output> $protocol _ " routes: "; | |||
'''for-each( .//rt-entry[protocol-name = $protocol] )''' { | |||
'''<output> ../rt-destination; ''' }}}} | |||
==Специальные переменные== | ==Специальные переменные== | ||
Строка 166: | Строка 222: | ||
*'''op scripts:''' | *'''op scripts:''' | ||
:*$param - глобальная переменная, значение которой можно поменять с помощью argument - если в command line задаем какое-то другое значение для param, то будет использоваться новое заданное. Если в command line не задаем никакое, то используется то значение, которое определено в спринте... Лучше посмотреть в примере ниже, станет все понятно... | |||
:*$argument - особая переменная, кот предоставляет доступные аргументы в command-line; | :*$argument - особая переменная, кот предоставляет доступные аргументы в command-line; | ||
param $protocol = 'Static'; | |||
var $arguments = { | |||
<argument> { | |||
<name> "protocol"; | |||
<description> 'Chouse your favorite protocol!'; }} | |||
match / { | |||
<op-script-results> { | |||
var $results = jcs:invoke("get-route-information"); | |||
for-each( $results ) { | |||
<output> $protocol _ " routes: "; | |||
for-each( .//rt-entry[protocol-name = $protocol] ) { | |||
<output> ../rt-destination; }}}} | |||
Вывод: | |||
blair> op route | |||
Static routes: | |||
10.20.0.0/16 | |||
10.21.0.0/16 | |||
10.22.0.0/16 | |||
10.23.0.0/16 | |||
blair> op route ? | |||
Possible completions: | |||
<[Enter]> Execute this command | |||
<name> Argument name | |||
detail Display detailed output | |||
protocol Chouse your favorite protocol! | |||
| Pipe through a command | |||
blair> op route protocol Local | |||
Local routes: | |||
192.168.86.9/32 | |||
192.168.86.18/32 | |||
192.168.86.26/32 | |||
192.168.86.50/32 | |||
192.168.88.1/32 | |||
*'''event scripts''' | *'''event scripts''' | ||
Строка 187: | Строка 271: | ||
<then> { | <then> { | ||
<event-script> { | <event-script> { | ||
<name> "check-var-utilization.slax"; | <name> "check-var-utilization.slax"; }}}}} | ||
*'''$junos-context''' - содержит информацию об устройстве, но для каждых типов скриптов имеет свой набор параметров. | *'''$junos-context''' - содержит информацию об устройстве, но для каждых типов скриптов имеет свой набор параметров. | ||
==If/then/else== | |||
Можно использовать следующим образом. В таком случае если переменная $mytree должно обрабатываться как $node-set, то ставим '''=''', если как простая переменная, то можно ''':='''. | |||
Структура, пример: | |||
var $this = 10; | |||
var $that = 5; | |||
var $mytree := { | |||
'''if ( $this < $that ) {''' | |||
<tree> "Pine"; | |||
<tree> "Birch"; '''}''' | |||
'''else if ( $this = $that ) {''' | |||
<tree> "Spruce"; | |||
<tree> "Fir"; '''}''' | |||
'''else {''' | |||
<tree> "Gooseberry"; | |||
<tree> "Juniper"; '''}'''} | |||
match / { | |||
<op-script-results> { | |||
for-each ( $mytree/tree) { | |||
<output> .; }}} - (.) стала указателем для каждой node в node-set, | |||
blair> op if-then | |||
Gooseberry | |||
Juniper | |||
==For-each== | |||
Структура: | |||
for-each( <xpath-expresssion> ) { | |||
... } | |||
Пример: | |||
match / { | |||
<op-script-results> { | |||
var $ge_ints := { | |||
var $results = jcs:invoke("get-interface-information"); | |||
for-each ($results/physical-interface[starts-with(name, "ge-")]/logical-interface) { | |||
var $ifd = .; | |||
<interface> { | |||
<name> $ifd/name; | |||
<ip-addr> $ifd/address-family/interface-address/ifa-destination; }}} | |||
for-each ($ge_ints/interface) { | |||
<output> "Interface " _ ./name _ " has IP addr of " _ ./ip-addr; }}} | |||
Вывод: | |||
blair> op for-each | |||
Interface ge-0/0/0.60 has IP addr of 192.168.86.8/30 | |||
Interface ge-0/0/0.80 has IP addr of 192.168.86.48/30 | |||
Interface ge-0/0/0.100 has IP addr of 192.168.86.16/30 | |||
Interface ge-0/0/0.110 has IP addr of 192.168.86.24/30 | |||
Interface ge-0/0/0.32767 has IP addr of | |||
Interface ge-0/0/1.0 has IP addr of 192.168.88.0/30 | |||
*'''$ge_ints''' будет содержать node-set вида, который мы создаем с помощью for-each: | |||
<interface 1> | |||
<name 1> | |||
<ip-addr 1> | |||
<interface 2> | |||
<name 2> | |||
<ip-addr 2> | |||
... | |||
*'''$results''' - будет выглядеть следующим образом: | |||
blair> show interfaces | display xml | |||
<rpc-reply xmlns:junos="http://xml.juniper.net/junos/12.1X47/junos"> | |||
<interface-information xmlns="http://xml.juniper.net/junos/12.1X47/junos-interface" junos:style="normal"> | |||
<physical-interface> | |||
<name>ge-0/0/0</name> | |||
<admin-status junos:format="Enabled">up</admin-status> | |||
<oper-status>up</oper-status> | |||
<local-index>134</local-index> | |||
<snmp-index>507</snmp-index> | |||
<link-level-type>Flexible-Ethernet</link-level-type> | |||
<mtu>1518</mtu> | |||
... | |||
*'''$ifd''' - путь = $results/physical-interface[starts-with(name, "ge-")]/logical-interface. | |||
==While, Do-While, For== | |||
Нет встроенного кода, но можно выкрутиться с помощью использования шаблонов. | |||
Пример While: | |||
match / { ''/*main block of code */'' | |||
call do-while-stuff(); } ''/* call the template 'do-while-stuff' */'' | |||
template do-while-stuff( $counter = 0 ) { | |||
if( $counter < $SOME-BIG-VALUE ) { | |||
... | |||
call do-while-stuff( $counter = $counter + SOME-INC-VALUE ); }} ''/* recursive template call */'' | |||
Пример Do-while: | |||
match / { | |||
var $status = get-first-status(); | |||
call do-while-status-not-down( $status ); } ''/* call the recursive template */'' | |||
template do-while-status-not-down( $status ) { | |||
... | |||
var $new-status = get-new-status(); | |||
if( $new-status != "down" ) { | |||
call do-while-status-not-down( $status = $new-status ); }} | |||
Пример для For: | |||
match / { | |||
call do-ten-times(); } | |||
template do-ten-times( $counter = 0 ) { | |||
if( $counter < 10 ) { | |||
... | |||
call do-ten-times( $counter = $counter + 1 ); } } | |||
=Code modularity= | |||
==Importing files== | |||
При создании импортируемого файла следим, чтобы: | |||
*Импортируемый файл должен находиться в той же директории, что и основной скрипт. | |||
*Нужно определить свое пространство имен, чтобы избежать пересечений. | |||
ns '''bob''' = http://xml.bob.com/junos; | |||
template '''bob''':get-status() { ... } | |||
Когда файл создан, как его импортировать: | |||
*Объявляем пространство имен (namespace) | |||
ns bob = "http://xml.bob.com/junos" | |||
*Импортируем файл: | |||
import "bob-script.slax"; | |||
'''Примечание!''': импортируемые файлы хранятся на Juniper в /var/run/scripts/import. Эта директория - red only, поэтому туда нельзя пихать lib. Для lib нужно создать отдельную папку. | |||
==Templates== | |||
Именованный шаблон: его можно вызвать. Он имеет параметры, которые либо могут иметь дефолтные значения, либо значение параметров обязательно нужно указывать при вызове шаблона. | |||
template template-1( $num = 0, $descr ) | |||
{ ... } | |||
call template-1( $descr = "Bob", $num = 5 ); | |||
Шаблоны порождают RTF (result tree fragment) | |||
==Functions== | |||
Нет отдельного синтаксиса для функций, чтобы они работали, требуется: | |||
*Включить: | |||
ns func extension = "http://exslt.org/functions"; | |||
*Объявить: | |||
ns mycorp = "http://xml.mycorp.com/junos" | |||
*Вызвать | |||
<func:function name="mycorp:incr"> { | |||
param $num; | |||
<func:result select="number($num+1)">; } | |||
Результат выполнения функции - любой типа данных, что положительно отличает ее от шаблона. | |||
=Using Junos RPC - Remote Procedure calls= | |||
==Creating request== | |||
Чтобы правильно составить запрос смотрим как будет выглядеть тот самый запрос с помощью: | |||
blair> show interfaces | '''display xml rpc''' | |||
<rpc-reply xmlns:junos="http://xml.juniper.net/junos/12.1X47/junos"> | |||
<rpc> | |||
'''<get-interface-information>''' | |||
</get-interface-information> | |||
</rpc> | |||
<cli> | |||
<banner></banner> | |||
</cli> | |||
</rpc-reply> | |||
==Execution== | |||
var $var = <get-interface-information> { | |||
<detail>; } | |||
var $result = jcs:invoke( $var ); | |||
Либо просто: | |||
var $result = jcs:invoke( 'get-interface-information' ); | |||
Результат выполнения - node-set, в нашем случае будет хранится в переменной $result. | |||
Для проверки возвращает ли переменная ошибку, можно использовать: | |||
if( $result//self::xnm:error ) { } | |||
В случае, когда требуется выполнить много RPC, будет более эффективно установить соединение с management deamon, стянуть все RPC, закрыть соединение. | |||
*'''jcs:open()''' - без параметров - устанавливает соединение с management deamon локального Juniper устройства. | |||
var $connection = jcs:open(); | |||
*С параметрами - можно установить соединение с удаленным устройством. | |||
var $remote-dev = jcs:open( "192.168.17.22", "admin", "admin123" ); | |||
*'''jcs:execute()''' - после открытого соединения, начинает выполнять RPC. (в выводе учитываются вышеописанные переменные) | |||
var $interface = jcs:execute( $connection, 'get-interface-information' ); | |||
var $interface-detail = jcs:execute( $connection, $var ); | |||
*'''jcs:close()''' - закрывает соединение | |||
expr jcs:close( $connection ); | |||
==Using Results== | |||
Результат - node set! = blair> show interfaces | display xml | |||
Переменная будет установлена в тот блок XML вывода, который следует сразу за <rpc-reply>. | |||
Таким образом, если требуется запустить '''for-each''' для определенного блока во всем node-set, то нужно будет прописать "путь" (XPath) до этого "блока"! | |||
match / { <op-script-results> { | |||
var $inventory = jcs:invoke( 'get-chassis-inventory' ); | |||
for-each( $inventory/chassis/chassis-module[serial-number] ) { | |||
<output> "Module " _ name _ ", serial number: " _ serial-number; | |||
=Consol input/output= | |||
==jcs:get-input() and jcs:get-secret()== | |||
Используются в op-scripts для возможности ввода юзером текста. | |||
*jcs:get-input() - отображает ввод юзера | |||
*jcs:get-secret() - не отображает ввод юзера | |||
match / { | |||
<op-script-results> { | |||
var $host = jcs:get-input ("Enter host-name: "); | |||
var $user = jcs:get-input ("Enter user-name: "); | |||
var $pass = jcs:get-secret ("Enter password: "); | |||
var $connection = jcs:open ($host, $user, $pass); | |||
if ($connection) { | |||
var $result = jcs:execute ($connection, "get-software-inforamtion"); | |||
var $version = $result/..//software-information[1]/package-information[name=='junos']; | |||
expr jcs:output ("Junos version: ", $version/comment); } | |||
else { | |||
expr jcs:output("Unable to open a connection."); }}} | |||
blair> op login | |||
Enter host-name: 10.200.86.8 | |||
Enter user-name: bob | |||
Enter password: | |||
==<output> and jcs:output() and jcs:printf()== | |||
*<output> and jcs:output() - в op-scripts посылают вывод строки в консоль. Не используются в commit-scripts, | |||
match / { | |||
<op-script-results> { | |||
<output> "1"; | |||
<output> "2"; | |||
expr jcs:output ("3"); | |||
expr jcs:output ("4"); | |||
<output> "5"; }} | |||
blair> op output | |||
3 | |||
4 | |||
1 | |||
2 | |||
5 | |||
*jcs:printf() - создает и запоминает форматированную строку, которую можно будет запихнуть в консоль, файл или переменную. | |||
match / { | |||
<op-script-results> { | |||
expr jcs:output (jcs:printf( "%15s %-10s", "Parameter", "Value" )); | |||
expr jcs:output (jcs:printf( "%15s %-10s", "$user", $user ) ); | |||
expr jcs:output (jcs:printf( "%15s %-10s", "$hostname", $hostname )); | |||
expr jcs:output (jcs:printf( "%15s %-10s", "$script", $script ) ); | |||
expr jcs:output (jcs:printf( "%15s %-10s", "$localtime", $localtime )); | |||
var $string = jcs:get-input( "Enter string: " ); | |||
var $width = jcs:get-input( "Enter width: " ); | |||
var $precision = jcs:get-input( "Enter precision: " ); | |||
expr jcs:output( jcs:printf( "|%*.*s|", $width, $precision, $string ) ); }} | |||
blair> op output | |||
Parameter Value | |||
$user bob | |||
$hostname blair | |||
$script output.slax | |||
$localtime Sun Jan 8 23:20:02 2017 | |||
Enter string: test | |||
Enter width: 50 | |||
Enter precision: oppa | |||
==<xsl:message>== | |||
*<xsl:message> - вывод ошибки. Лучше с осторожностью использовать в commit-scripts. | |||
Можно использовать как "exit" or "die", если подставить параметр <xsl:message '''terminate="yes"'''> | |||
<xsl:message terminate="yes"> "Script has ended"> | |||
error: Script has ended | |||
==jcs:progress()== | |||
*jcs:progress() - используем для дебага. | |||
match / { | |||
<op-script-results> { | |||
expr jcs:progress("Staritng scanning proccess"); | |||
... | |||
blair> op output '''detail''' | |||
2017-01-08 23:32:34 UTC: reading op script input details | |||
2017-01-08 23:32:34 UTC: testing op details | |||
2017-01-08 23:32:34 UTC: running op script 'output.slax' | |||
2017-01-08 23:32:34 UTC: opening op script '/var/db/scripts/op/output.slax' | |||
2017-01-08 23:32:34 UTC: reading op script 'output.slax' | |||
'''2017-01-08 23:32:34 UTC: Staritng scanning proccess''' | |||
3 | |||
4 | |||
Parameter Value | |||
$user vlad | |||
$hostname blair | |||
$script output.slax | |||
$localtime Sun Jan 8 23:32:34 2017 | |||
Enter string: f | |||
Enter width: g | |||
Enter precision: t | |||
|| | |||
error: Script has ended | |||
2017-01-08 23:32:38 UTC: inspecting op output 'output.slax' | |||
1 | |||
2 | |||
5 | |||
2017-01-08 23:32:38 UTC: finished op script 'output.slax' | |||
==<xnm:warning> and <xnm:error>== | |||
*xnm:warning - просто выведет предупреждающее сообщение при коммите: | |||
match configuration { | |||
<xnm:warning> { | |||
<message> "Successfull commit! Congrats!"; }} | |||
blair# commit and-quit | |||
'''warning: Successfull commit! Congrats!''' | |||
commit complete | |||
Exiting configuration mode | |||
*xnm:error - выводит ошибку при коммите и на дает candidate config стать active config. | |||
match configuration { | |||
<xnm:error> { | |||
<message> "No!No!No!Nononononooooo!"; }} | |||
blair# commit and-quit | |||
error: No!No!No!Nononononooooo! | |||
error: 1 error reported by commit scripts | |||
error: commit script failure | |||
=Storage Input/Output= | |||
==Reading from files== | |||
*document() - функция для чтения XML файла локальной файловой системы. | |||
var $chassis-info = document("/var/home/jnpr/chassis-inventory.xml"); | |||
*<file-get> + jcs:break-lines() - если файл не XML, то можно использовать <file-get> RPC и затем разбивать файл на строки с помощью петли jcs:break-lines() | |||
Пример в книге | |||
==Writing to file== | |||
*<file-put> RPC + <exsl:document>/<redirect:write> element - если не XML. <file-put> - нельзя использовать для записи в XML файл. Приемущество - можно ограничивать доступ. | |||
Пример в книге | |||
*<exsl:document> - хороший функционал, но нельзя ограничивать доступ. При использовании этого элемента - требуется обязательно обозначить: ''ns exsl extension = "http://exslt.org/common";'' | |||
Пример в книге | |||
*<redirect:write> - можно добавлять данные к уже существующему файлу. Но нельзя контролировать доступ. Требуется обозначить: ''ns redirect extension = "org.apache.xalan.xslt.extensions.Redirect";'' | |||
Пример в книге. | |||
==Writing to Syslog== | |||
* jcs:syslog() - состоит из: facility/severity и потом запятыми разделяются остальные параметры. | |||
expr jcs:syslog( "external.info", "Example syslog message: \"Insert message here\"" ); | |||
expr jcs:syslog( "daemon.debug", "This message is from ", $script ); | |||
==Writing to traceoptions== | |||
*jcs:trace() - можно включить для разных типов скриптов. В скобках могут быть указаны через запятую те строки или записи, которые можно записать в traceoption file. | |||
match /{ | |||
<op-script-results> { | |||
expr jcs:trace ("This is test write: "); | |||
<output> "Hello World!"; }} | |||
На Juniper: | |||
blair> show configuration system scripts | |||
op { | |||
traceoptions { | |||
file opscript; | |||
flag input; | |||
flag rpc; } | |||
file hello-world.slax; | |||
file traceop.slax; | |||
vlad@blair> show log opscript | |||
Jan 10 22:06:16 complete script processing begins | |||
Jan 10 22:06:17 opening op script '/var/db/scripts/op/traceop.slax' | |||
Jan 10 22:06:17 reading op script 'traceop.slax' | |||
Jan 10 22:06:17 op script processing begins | |||
... | |||
=Полезные ссылки= | |||
* [https://github.com/phylocko/slax-tools Скрипты для автоматизации рутинных задач в JunOS] | |||
=Дополнительная информация= | |||
*[[Общие сведения об автоматизации в JunOS]] | |||
*[[Пример программы на SLAX]] |
Текущая версия на 18:07, 15 июля 2021
Fundamentals
- SLAX is based on XPath 1.0 and XSLT 1.0.
- Переменные задаются только 1 раз и дальше уже нет возможности их изменить.
- The “Node-Set” variable data-type is from XPath. Используется оператор :=.
var $my-ns-var := { <interface> { <name> "ge-0/0/0"; } <interface> { <name> "ge-0/0/1";
- The “Result Tree Fragment” (RTF) может хранить в себе как строку, так и блок XML данных. Используется оператор =.
var $my-ns-var = { <interface> { <name> "ge-0/0/0"; } <interface> { <name> "ge-0/0/1";
- Шаблон по умолчанию вернет RTF, но с помощью использования := можео заставить его вернуть node-set.
- Разница между node-set и RTF - в знаке назначения переменной.
- Context Processing. Насколько я поняла, можно использовать (.) как current context. Пример:
var $my := { <interface> { <name> "ge-0/0/0"; <descr> 'interface 1'; } <interface> { <name> "ge-0/0/1"; <descr> 'interface 2';}} match / { <op-script-results> { for-each ($my/interface) { <output> ./name; <output> ./descr; }}
SLAX Script File Structure
Для просмотра в XML формате используем:
blair> show configuration | display xml
Comments: - можно располагать хоть где в коде.
- (/*) - open-comment
- (*/) - close-comment
- At the top: В скриптах в начале обязательно добавляется:
version 1.0; ns junos = "http://xml.juniper.net/junos/*/junos"; ns xnm = "http://xml.juniper.net/xnm/1.1/xnm"; ns jcs = "http://xml.juniper.net/junos/commit-scripts/1.0"; Import "../import/junos.xsl";
после junos.xsl можно добавить любые свои файлы, написанные на SLAX, XSLT или их комбинации.
- param - не изменяемые, нужно таким образом задать параметры командной строки.
param $interface; param $mtu-size = 2000;
можно задать переменную, например mtu-size, и если юзер не будет указывать напрямую значение нужного параметра, то будет использоваться то значение, которое указано через param.
- $arguments - глобальная переменная, обеспечивает полезную информацию из CLI. Предоставляет доступные аргументы в command-line;
- $event-definition - глобальная переменная, задающая условия для выполнения скрипта.
- main template - концептуальное начало скрипта. Шаблон, который матчит первый элемент из input XML document (source tree), даже если он пустой.
Для op script - выглядит так:
match / { <op-script-results> { /* your code goes here */
Для event script - выглядит так:
match / { <event-script-results> { /* your code goes here */
Для commit script - выглядит так:
match configuration { /* your code goes here */
Variables
Простые типы данных
Начало: var $xxx
Нельзя использовать в начале juno, jcs, xnm, slax. Нельзя: $arguments, $event-definition
Можно использовать : в названии переменной. Удобно, при создании библиотек, и когда скрипт будет вложен в скрипт.
Переменную можно объявить:
- глобально - только эти переменные могут быть "перезаписаны" дальше в коде, лучше этого не допускать.
- в шаблоне/функции
- внутри программного control-block (for-each and the if/then/else)
Типы данных: строка (string), число (number), логическая (boolean), node-set.
- Boolean: true() and false(). boolean() - функция переделывает выражение в логические. 0 = false, non-0 = true и т.д. Empty RTF coverts to true.
- Numbers: целые или дробные (.)
- Strings: " " или ' '. string() - функция превращает аргумент в строку.
- Result Tree Fragments: можем использовать как script output, обработать как строку, или конвертировать в node-set. При объявлении переменной такого типа нельзя заканчивать ; после }. Каждый шаблон возвращает RTF.
- Node-set: =RTF, отличие от RTF - при присвоении переменной значения используем :=. XPath expressions работают только с node-sets. Также отличие - к node-set можно применять операторы сравнения. ( | ) - оператор для создания комбинации node-sets.
Пример: for-each( ($primary-colors | $fav-colors)/color ) {...
Сложные структуры данных
- массивы - нет массивов как таковых, но можно использовать node-set XPath expressions особым образом.
Элементы массива начинаются с 1, а не с 0.
Пример задания массива:
var $interfaces := { <int> "ge-0/0/0"; <int> "ge-0/0/1"; <int> "ge-0/0/2"; <int> "ge-0/0/3"; <int> "ge-1/0/1"; <int> "ge-1/0/2"; <int> "ge-1/0/3"; <int> "ge-1/0/4"; } match / { <op-script-results> { <output> "interface 5 = " _ $interfaces/int[5] _ "\n"; <output> "all interfaces:\n"; for-each ($interfaces/int) { <output> .; }}}
Вывод:
blair> op arrays interface 5 = ge-1/0/1 all interfaces: ge-0/0/0 ge-0/0/1 ge-0/0/2 ge-0/0/3 ge-1/0/1 ge-1/0/2 ge-1/0/3 ge-1/0/4
- хэши - (аналог Dictionary в Python) нет хешей как таковых, но можно использовать node-set XPath expressions особым образом.
Пример: hash1 - где key - status - аттрибут данных (значение элемента int), $hash2 - key - mtu - элемент данных (элемент int, такой же как и int).
var $hash1 := { <int name = "ge-0/0/0"> "down"; <int name = "ae2"> "up"; } var $hash2 := { <int> { <name> "ge-0/0/0"; <mtu> "1500"; } <int> { <name> "ae8"; <mtu> "2000"; }} match / { <op-script-results> { /*Interface status in $hash1*/ <output> "ge-0/0/0 is " _ $hash1/int[@name == 'ge-0/0/0']; <output> "ae2 is " _ $hash1/int[@name == 'ae2']; /*Interface MTU from $hash2*/ <output> "MTU ae8 = " _ $hash2/int[name=='ae8']/mtu; }}
Вывод:
blair> op hash ge-0/0/0 is down ae2 is up MTU ae8 = 2000
Еще один метод использования hash - XSLT <xsl:key> top-level element and key() function.
key - используются для индексирования node внутри XML.
<xsl:key> top-level element - содержит name, node index, ссылку на нужный node.
key () - функция, которая "достает" нужный node. Содержит имя key и желаемое значение key.
/* Define <xsl:key> elements */ <xsl:key name="protocol" match="route-table/rt" use="rt-entry/protocol-name">; <xsl:key name="next-hop" match="route-table/rt" use="rt-entry/nh/via">; var $match-protocol = "Static"; /* we want to match on "static" routes */ var $match-interface= "ge-0/0/0.60"; /*we want to match on routes via interface ge-0/0/0.60 */ match / { <op-script-results> { var $results = jcs:invoke("get-route-information"); /* Change current node to the $results XML document */ for-each( $results ) { /* Display all static routes */ <output> $match-protocol _ " routes: "; for-each( key( "protocol", $match-protocol ) ) { <output> rt-destination; } /* Display all routes with next-hop of ge-0/0/0.60 */ <output> "Next-hop " _ $match-interface _ ": "; for-each( key( "next-hop", $match-interface ) ) { <output> rt-destination; }
Вывод: blair> op route-info Static routes: 6.6.6.6/32 Next-hop ge-0/0/0.60: 10.200.86.5/32 10.200.86.6/32 10.200.86.7/32 192.168.86.4/30 192.168.86.8/30 192.168.86.28/30 192.168.86.44/30
И последний способ:
var $protocol = 'Static'; match / { <op-script-results> { var $results = jcs:invoke("get-route-information"); for-each( $results ) { <output> $protocol _ " routes: "; for-each( .//rt-entry[protocol-name = $protocol] ) { <output> ../rt-destination; }}}}
Специальные переменные
- Общие:
- $product - name of the Junos device
- $user - user, запускающий скрипт
- $hostname - hostname
- $script - скрипт, который запускаем
- $localtime - время, когда был запущен скрипт. Вид: Fri Jan 7 14:07:33 2011
- $localtime-iso - 2011-01-08 03:38:57 UTC
- op scripts:
- $param - глобальная переменная, значение которой можно поменять с помощью argument - если в command line задаем какое-то другое значение для param, то будет использоваться новое заданное. Если в command line не задаем никакое, то используется то значение, которое определено в спринте... Лучше посмотреть в примере ниже, станет все понятно...
- $argument - особая переменная, кот предоставляет доступные аргументы в command-line;
param $protocol = 'Static'; var $arguments = { <argument> { <name> "protocol"; <description> 'Chouse your favorite protocol!'; }} match / { <op-script-results> { var $results = jcs:invoke("get-route-information"); for-each( $results ) { <output> $protocol _ " routes: "; for-each( .//rt-entry[protocol-name = $protocol] ) { <output> ../rt-destination; }}}}
Вывод:
blair> op route Static routes: 10.20.0.0/16 10.21.0.0/16 10.22.0.0/16 10.23.0.0/16 blair> op route ? Possible completions: <[Enter]> Execute this command <name> Argument name detail Display detailed output protocol Chouse your favorite protocol! | Pipe through a command blair> op route protocol Local Local routes: 192.168.86.9/32 192.168.86.18/32 192.168.86.26/32 192.168.86.50/32 192.168.88.1/32
- event scripts
- $event-definition - определяет условия для срабатывания скрипта
var $event-definition = { <event-options> { <generate-event> { <name> "every-hour"; <time-interval> "3600"; } <policy> { <name> "check-var-utilization"; <events> "every-hour"; <then> { <event-script> { <name> "check-var-utilization.slax"; }}}}}
- $junos-context - содержит информацию об устройстве, но для каждых типов скриптов имеет свой набор параметров.
If/then/else
Можно использовать следующим образом. В таком случае если переменная $mytree должно обрабатываться как $node-set, то ставим =, если как простая переменная, то можно :=. Структура, пример:
var $this = 10; var $that = 5; var $mytree := { if ( $this < $that ) { <tree> "Pine"; <tree> "Birch"; } else if ( $this = $that ) { <tree> "Spruce"; <tree> "Fir"; } else { <tree> "Gooseberry"; <tree> "Juniper"; }} match / { <op-script-results> { for-each ( $mytree/tree) { <output> .; }}} - (.) стала указателем для каждой node в node-set,
blair> op if-then Gooseberry Juniper
For-each
Структура:
for-each( <xpath-expresssion> ) { ... }
Пример:
match / { <op-script-results> { var $ge_ints := { var $results = jcs:invoke("get-interface-information"); for-each ($results/physical-interface[starts-with(name, "ge-")]/logical-interface) { var $ifd = .; <interface> { <name> $ifd/name; <ip-addr> $ifd/address-family/interface-address/ifa-destination; }}} for-each ($ge_ints/interface) { <output> "Interface " _ ./name _ " has IP addr of " _ ./ip-addr; }}} Вывод: blair> op for-each Interface ge-0/0/0.60 has IP addr of 192.168.86.8/30 Interface ge-0/0/0.80 has IP addr of 192.168.86.48/30 Interface ge-0/0/0.100 has IP addr of 192.168.86.16/30 Interface ge-0/0/0.110 has IP addr of 192.168.86.24/30 Interface ge-0/0/0.32767 has IP addr of Interface ge-0/0/1.0 has IP addr of 192.168.88.0/30
- $ge_ints будет содержать node-set вида, который мы создаем с помощью for-each:
<interface 1> <name 1> <ip-addr 1> <interface 2> <name 2> <ip-addr 2> ...
- $results - будет выглядеть следующим образом:
blair> show interfaces | display xml <rpc-reply xmlns:junos="http://xml.juniper.net/junos/12.1X47/junos"> <interface-information xmlns="http://xml.juniper.net/junos/12.1X47/junos-interface" junos:style="normal"> <physical-interface> <name>ge-0/0/0</name> <admin-status junos:format="Enabled">up</admin-status> <oper-status>up</oper-status> <local-index>134</local-index> <snmp-index>507</snmp-index> <link-level-type>Flexible-Ethernet</link-level-type> <mtu>1518</mtu> ...
- $ifd - путь = $results/physical-interface[starts-with(name, "ge-")]/logical-interface.
While, Do-While, For
Нет встроенного кода, но можно выкрутиться с помощью использования шаблонов.
Пример While:
match / { /*main block of code */ call do-while-stuff(); } /* call the template 'do-while-stuff' */ template do-while-stuff( $counter = 0 ) { if( $counter < $SOME-BIG-VALUE ) { ... call do-while-stuff( $counter = $counter + SOME-INC-VALUE ); }} /* recursive template call */
Пример Do-while:
match / { var $status = get-first-status(); call do-while-status-not-down( $status ); } /* call the recursive template */ template do-while-status-not-down( $status ) { ... var $new-status = get-new-status(); if( $new-status != "down" ) { call do-while-status-not-down( $status = $new-status ); }}
Пример для For:
match / { call do-ten-times(); } template do-ten-times( $counter = 0 ) { if( $counter < 10 ) { ... call do-ten-times( $counter = $counter + 1 ); } }
Code modularity
Importing files
При создании импортируемого файла следим, чтобы:
- Импортируемый файл должен находиться в той же директории, что и основной скрипт.
- Нужно определить свое пространство имен, чтобы избежать пересечений.
ns bob = http://xml.bob.com/junos; template bob:get-status() { ... }
Когда файл создан, как его импортировать:
- Объявляем пространство имен (namespace)
ns bob = "http://xml.bob.com/junos"
- Импортируем файл:
import "bob-script.slax";
Примечание!: импортируемые файлы хранятся на Juniper в /var/run/scripts/import. Эта директория - red only, поэтому туда нельзя пихать lib. Для lib нужно создать отдельную папку.
Templates
Именованный шаблон: его можно вызвать. Он имеет параметры, которые либо могут иметь дефолтные значения, либо значение параметров обязательно нужно указывать при вызове шаблона.
template template-1( $num = 0, $descr ) { ... } call template-1( $descr = "Bob", $num = 5 );
Шаблоны порождают RTF (result tree fragment)
Functions
Нет отдельного синтаксиса для функций, чтобы они работали, требуется:
- Включить:
ns func extension = "http://exslt.org/functions";
- Объявить:
ns mycorp = "http://xml.mycorp.com/junos"
- Вызвать
<func:function name="mycorp:incr"> { param $num; <func:result select="number($num+1)">; }
Результат выполнения функции - любой типа данных, что положительно отличает ее от шаблона.
Using Junos RPC - Remote Procedure calls
Creating request
Чтобы правильно составить запрос смотрим как будет выглядеть тот самый запрос с помощью:
blair> show interfaces | display xml rpc <rpc-reply xmlns:junos="http://xml.juniper.net/junos/12.1X47/junos"> <rpc> <get-interface-information> </get-interface-information> </rpc> <cli> <banner></banner> </cli> </rpc-reply>
Execution
var $var = <get-interface-information> { <detail>; } var $result = jcs:invoke( $var );
Либо просто:
var $result = jcs:invoke( 'get-interface-information' );
Результат выполнения - node-set, в нашем случае будет хранится в переменной $result.
Для проверки возвращает ли переменная ошибку, можно использовать:
if( $result//self::xnm:error ) { }
В случае, когда требуется выполнить много RPC, будет более эффективно установить соединение с management deamon, стянуть все RPC, закрыть соединение.
- jcs:open() - без параметров - устанавливает соединение с management deamon локального Juniper устройства.
var $connection = jcs:open();
- С параметрами - можно установить соединение с удаленным устройством.
var $remote-dev = jcs:open( "192.168.17.22", "admin", "admin123" );
- jcs:execute() - после открытого соединения, начинает выполнять RPC. (в выводе учитываются вышеописанные переменные)
var $interface = jcs:execute( $connection, 'get-interface-information' ); var $interface-detail = jcs:execute( $connection, $var );
- jcs:close() - закрывает соединение
expr jcs:close( $connection );
Using Results
Результат - node set! = blair> show interfaces | display xml
Переменная будет установлена в тот блок XML вывода, который следует сразу за <rpc-reply>.
Таким образом, если требуется запустить for-each для определенного блока во всем node-set, то нужно будет прописать "путь" (XPath) до этого "блока"!
match / { <op-script-results> { var $inventory = jcs:invoke( 'get-chassis-inventory' ); for-each( $inventory/chassis/chassis-module[serial-number] ) { <output> "Module " _ name _ ", serial number: " _ serial-number;
Consol input/output
jcs:get-input() and jcs:get-secret()
Используются в op-scripts для возможности ввода юзером текста.
- jcs:get-input() - отображает ввод юзера
- jcs:get-secret() - не отображает ввод юзера
match / { <op-script-results> { var $host = jcs:get-input ("Enter host-name: "); var $user = jcs:get-input ("Enter user-name: "); var $pass = jcs:get-secret ("Enter password: "); var $connection = jcs:open ($host, $user, $pass); if ($connection) { var $result = jcs:execute ($connection, "get-software-inforamtion"); var $version = $result/..//software-information[1]/package-information[name=='junos']; expr jcs:output ("Junos version: ", $version/comment); } else { expr jcs:output("Unable to open a connection."); }}}
blair> op login Enter host-name: 10.200.86.8 Enter user-name: bob Enter password:
<output> and jcs:output() and jcs:printf()
- <output> and jcs:output() - в op-scripts посылают вывод строки в консоль. Не используются в commit-scripts,
match / { <op-script-results> { <output> "1"; <output> "2"; expr jcs:output ("3"); expr jcs:output ("4"); <output> "5"; }} blair> op output 3 4 1 2 5
- jcs:printf() - создает и запоминает форматированную строку, которую можно будет запихнуть в консоль, файл или переменную.
match / { <op-script-results> { expr jcs:output (jcs:printf( "%15s %-10s", "Parameter", "Value" )); expr jcs:output (jcs:printf( "%15s %-10s", "$user", $user ) ); expr jcs:output (jcs:printf( "%15s %-10s", "$hostname", $hostname )); expr jcs:output (jcs:printf( "%15s %-10s", "$script", $script ) ); expr jcs:output (jcs:printf( "%15s %-10s", "$localtime", $localtime )); var $string = jcs:get-input( "Enter string: " ); var $width = jcs:get-input( "Enter width: " ); var $precision = jcs:get-input( "Enter precision: " ); expr jcs:output( jcs:printf( "|%*.*s|", $width, $precision, $string ) ); }} blair> op output Parameter Value $user bob $hostname blair $script output.slax $localtime Sun Jan 8 23:20:02 2017 Enter string: test Enter width: 50 Enter precision: oppa
<xsl:message>
- <xsl:message> - вывод ошибки. Лучше с осторожностью использовать в commit-scripts.
Можно использовать как "exit" or "die", если подставить параметр <xsl:message terminate="yes">
<xsl:message terminate="yes"> "Script has ended"> error: Script has ended
jcs:progress()
- jcs:progress() - используем для дебага.
match / { <op-script-results> { expr jcs:progress("Staritng scanning proccess"); ...
blair> op output detail 2017-01-08 23:32:34 UTC: reading op script input details 2017-01-08 23:32:34 UTC: testing op details 2017-01-08 23:32:34 UTC: running op script 'output.slax' 2017-01-08 23:32:34 UTC: opening op script '/var/db/scripts/op/output.slax' 2017-01-08 23:32:34 UTC: reading op script 'output.slax' 2017-01-08 23:32:34 UTC: Staritng scanning proccess 3 4 Parameter Value $user vlad $hostname blair $script output.slax $localtime Sun Jan 8 23:32:34 2017 Enter string: f Enter width: g Enter precision: t || error: Script has ended 2017-01-08 23:32:38 UTC: inspecting op output 'output.slax' 1 2 5 2017-01-08 23:32:38 UTC: finished op script 'output.slax'
<xnm:warning> and <xnm:error>
- xnm:warning - просто выведет предупреждающее сообщение при коммите:
match configuration { <xnm:warning> { <message> "Successfull commit! Congrats!"; }}
blair# commit and-quit warning: Successfull commit! Congrats! commit complete Exiting configuration mode
- xnm:error - выводит ошибку при коммите и на дает candidate config стать active config.
match configuration { <xnm:error> { <message> "No!No!No!Nononononooooo!"; }}
blair# commit and-quit error: No!No!No!Nononononooooo! error: 1 error reported by commit scripts error: commit script failure
Storage Input/Output
Reading from files
- document() - функция для чтения XML файла локальной файловой системы.
var $chassis-info = document("/var/home/jnpr/chassis-inventory.xml");
- <file-get> + jcs:break-lines() - если файл не XML, то можно использовать <file-get> RPC и затем разбивать файл на строки с помощью петли jcs:break-lines()
Пример в книге
Writing to file
- <file-put> RPC + <exsl:document>/<redirect:write> element - если не XML. <file-put> - нельзя использовать для записи в XML файл. Приемущество - можно ограничивать доступ.
Пример в книге
- <exsl:document> - хороший функционал, но нельзя ограничивать доступ. При использовании этого элемента - требуется обязательно обозначить: ns exsl extension = "http://exslt.org/common";
Пример в книге
- <redirect:write> - можно добавлять данные к уже существующему файлу. Но нельзя контролировать доступ. Требуется обозначить: ns redirect extension = "org.apache.xalan.xslt.extensions.Redirect";
Пример в книге.
Writing to Syslog
- jcs:syslog() - состоит из: facility/severity и потом запятыми разделяются остальные параметры.
expr jcs:syslog( "external.info", "Example syslog message: \"Insert message here\"" ); expr jcs:syslog( "daemon.debug", "This message is from ", $script );
Writing to traceoptions
- jcs:trace() - можно включить для разных типов скриптов. В скобках могут быть указаны через запятую те строки или записи, которые можно записать в traceoption file.
match /{ <op-script-results> { expr jcs:trace ("This is test write: "); <output> "Hello World!"; }}
На Juniper:
blair> show configuration system scripts op { traceoptions { file opscript; flag input; flag rpc; } file hello-world.slax; file traceop.slax; vlad@blair> show log opscript Jan 10 22:06:16 complete script processing begins Jan 10 22:06:17 opening op script '/var/db/scripts/op/traceop.slax' Jan 10 22:06:17 reading op script 'traceop.slax' Jan 10 22:06:17 op script processing begins ...