Основы автоматизации на SLAX

Материал из Juniper Exam Wiki
Перейти к навигации Перейти к поиску


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
...

Полезные ссылки

Дополнительная информация