Pull to refresh

Автоматизация в JunOS: пишем скрипты

Reading time 5 min
Views 9.4K
В жизни каждого системного администратора наступают моменты, когда возникает необходимость автоматизировать выполнение каких-то операций или, например, приходится часть своих полномочий делегировать подчиненным. Причем, желательно это сделать безопасно, а возможность накосячить свести к минимуму. Хорошо, если речь идет про сервер под управлением *NIX-системы — имеется превеликое множество предназначенных для этого инструментов. Но так везет не всегда...

Казалось бы, достаточно тривиальная задача — дать возможность младшему техническому персоналу навешивать и снимать blackhole community на маршрутизаторах под управлением JunOS максимально простым способом. Все бы хорошо, но bash + ssh + expect меня не особо устроил, perl + Net::OpenSSH / Net::SSH / etc — тоже. В процессе обдумывания задачи вспомнился Tcl в IOS, и тут оказалось, что в JunOS тоже есть аналогичные собственные средства автоматизации, изящные и реально мощные, и, как по мне, более удобные, чем Tcl. И самое удивительное — Google в рунете упоминаний о них почему-то не нашел.

Что ж, позволю себе попытаться этот пробел восполнить, хотя бы для общего образования, в объеме, позволяющем оценить «красоту игры». Итак, в JunOS есть встроенная возможность писать собственные скрипты и потом их использовать, как обычные команды, например так:

/* Создать новую запись, устанавливающую blackhole community */
user@junosbox> op blackhole set 10.0.0.1

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

Обеспечивается эта возможность компонентом JunOS Automation Tool Kit, позволяющий использовать встроенные возможности XML JunOS, который является стандартным компонентом JunOS и, соответственно, присутствует на любом устройстве под управлением JunOS. Основным инструментом для этого является скриптовый язык XSLT. И это, как мне кажется, плохая новость — писать на нем, конечно, удобнее, чем на Brainfuck, но все равно несколько своеобразно. Но есть новость и хорошая: для упрощения жизни администраторов ребята из Juniper Networks также реализовали поддержку SLAX. В JunOS до версии 11 поддерживается SLAX 1.0, в более новых — 1.1. В документации упоминается, что его синтаксис несколько напоминает perl. Это утверждение мне кажется несколько спорным, но, определенно, некоторое отдаленное сходство есть, так что оставим этот момент на совести авторов документации.

В JunOS имеется несколько классов скриптов:

  • commit — применяются для автоматизации процесса commit-а — например, для проверки соответствия изменений в конфигурации каким-то правилам и вывода сообщений и прерывания commit-а в случае обнаружения каких-то проблем; при вызове commit-а поочередно отрабатывают все активные commit-скрипты;
  • op — скрипты в привычном системному администратору понимании — применяются для выполнения стандартной последовательности действий, изменения формата вывода команд и т. п.;
  • event — скрипты, вызываемые при возникновении каких-то событий;
  • SNMP — скрипты, вызываемые при обращении к SNMP-агенту системы

Очевидно, наиболее востребованными являются op-скрипты, хотя, и другим тоже найдется применение.

Чтобы установить скрипт в системе, следует его скопировать в соответствующий подкаталог каталога /var/db/scripts, после чего для его активации выполнить команду edit system scripts type file filename. После активации скрипта его можно вызвать, как любую другую разрешенную пользователю команду в operation mode: op filename arguments. Приятно, что допустимые параметры активированного скрипта CLI по нажатию "?" показывает точно так же, как и для встроенных системных команд. Аналогично показывается help, если его предусмотреть:

user@junosbox> op blackhole ?
Possible completions:
  <[Enter]>            Execute this command
  <name>               Argument name
  detail               Display detailed output
  set                  Set blackhole community for IP address
  |                    Pipe through a command

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

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

  1. Помните, JunOS — это XML. Самой необходимой командой при написании скриптов является show configuration… | display xml. Только так можно понять, какие темплейты и параметры следует использовать для построения новой конфигурации или внесения изменений в имеющуюся;
  2. Документация не всегда соответствует действительности. Так, например, в SLAX 1.0 им. JunOS задекларировать функцию путем использования <func:function> толком так и не получилось, поскольку интерпретатор ругался на все упоминания «var», «param» и т. п. внутри нее. При этом, аналогичного результата получилось добиться путем использования пользовательских темплейтов, про которые в документации Juniper Networks как раз ничего и не написано (см. пример);
  3. В функциях имеет значение порядок следования аргументов; в темплейтах он значения не имеет — здесь важно их название (опять же, см. пример);
  4. В XSLT и SLAX очень плохо с variable reuse — значение переменной, будучи единожды назначенным, изменяться в общем случае не может. Теоретически этот вопрос снимается использованием mvar (mutable var) вместо var для декларирования переменных, но в некоторых ситуациях их поведение становится странным (теряются присвоенные значения). Поэтому, поскольку документация говорит, что много переменных — это нормально и просто стоит принять этот нюанс синтаксиса к сведению, наверное, стоит так и поступить.

Ну и, напоследок:

Пример - op/blackhole.slax
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";
ns ext = "http://xmlsoft.org/XSLT/namespace";
import "../import/junos.xsl";

var $arguments = {
  <argument> {
    <name> "set";
    <description> "Set blackhole community for IP address";
  }
}
param $set;

match / {
  <op-script-results> {
    var $instance = "INSTANCE";
    var $community = "65535:666";
    if ($set) {
      var $addr = jcs:parse-ip($set);
      var $ip = $addr[1] _ "/32";
      var $connection = jcs:open();
      call set_community($connection, $ip, $instance, $community);
      var $close-results = jcs:close($connection);
      if ($close-results) { expr jcs:output($close-results); }
    }
    else {
      call show_community($community);
    }
  }
}

template set_community($connection, $ip, $instance, $community) {
  var $configuration = <configuration> {
    <routing-instances> {
      <instance> {
        <name> $instance;
        <routing-options> {
          <static> {
            <route> {
              <name> $ip;
              <discard>;
              <community> { <name> $community; }
            }
          }
        }
      }
    }
  }
  var $commit-options := { <commit-options> { <log> "setting blackhole community for " _ $ip; } }
  var $results := { call jcs:load-configuration($connection, $configuration, $commit-options); }
  copy-of $results;
}

template show_community($community) {
  var $command = { <command> "show route community " _ $community; }
  var $results = jcs:invoke($command);
  copy-of $results;
}

Ссылки:

Tags:
Hubs:
+10
Comments 1
Comments Comments 1

Articles