junoser internals
junoser というRuby gem をメンテしている. これはJUNOS Config のPEG パーサーで,標準入力やファイルを JUNOS Config として構文解析できる.解析後,文法チェックやスタイルの変換もできる.
たとえば,
構文チェック.display | set
形式とデフォルトの構造化スタイル両方をサポート.
$ junoser -c config.invalid Invalid syntax: set protocols bgp group e1 neighbor 10.0.26.1 exprot s2b zsh: exit 1 junoser -c config.invalid
スタイルの変換.display | set
スタイル → 構造化スタイル
junoser -s config.set
スタイルの変換.構造化スタイル → display | set
スタイル
junoser -d config
さっとチェックしたり,CI に使ったり,レビュー時 display | set
スタイルがつらい時に重宝してる.
文法定義はどこから来るか
CLI と,NETCONF 用のXML が構造的に似ていることを利用して,<get-schema>
で取れるXML Schema(XSD) を使っている.
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" xmlns:junos="http://xml.juniper.net/junos/12.1X47/junos"> <xsd:import schemaLocation="junos.xsd" namespace="http://xml.juniper.net/junos/12.1X47/junos"/> <xsd:element name="configuration"> <xsd:complexType> <xsd:sequence> <xsd:choice minOccurs="0" maxOccurs="unbounded"> <xsd:element name="interfaces" minOccurs="0"> <xsd:complexType> <xsd:sequence> <xsd:choice minOccurs="0" maxOccurs="unbounded"> <xsd:element name="interface" minOccurs="0" maxOccurs="unbounded" type="interfaces-type"> </xsd:element> </xsd:choice> </xsd:sequence> </xsd:complexType> </xsd:element> </xsd:choice> </xsd:sequence> </xsd:complexType> </xsd:element> <xsd:complexType name="interfaces-type"> <xsd:sequence> <xsd:element name="name"> <xsd:complexType> <xsd:simpleContent> <xsd:restriction base="key-attribute-string-type"> <xsd:enumeration value="$junos-interface-ifd-name"> </xsd:enumeration> <xsd:enumeration value="interface-name"> </xsd:enumeration> </xsd:restriction> </xsd:simpleContent> </xsd:complexType> </xsd:element> <xsd:choice minOccurs="0" maxOccurs="unbounded"> <xsd:element name="unit" minOccurs="0" maxOccurs="unbounded"> <xsd:complexType> <xsd:sequence> <xsd:element name="name"> <xsd:complexType> <xsd:simpleContent> <xsd:restriction base="key-attribute-ulong-type"> <xsd:enumeration value="$junos-underlying-interface-unit"> </xsd:enumeration> <xsd:enumeration value="$junos-interface-unit"> </xsd:enumeration> <xsd:enumeration value="interface-unit-number"> </xsd:enumeration> </xsd:restriction> </xsd:simpleContent> </xsd:complexType> </xsd:element> <xsd:choice minOccurs="0" maxOccurs="unbounded"> <xsd:element name="family" minOccurs="0"> <xsd:complexType> <xsd:sequence> <xsd:choice minOccurs="0" maxOccurs="unbounded"> <xsd:element name="inet" minOccurs="0"> <xsd:complexType> <xsd:sequence> <xsd:choice minOccurs="0" maxOccurs="unbounded"> <xsd:element name="address" minOccurs="0" maxOccurs="unbounded"> <xsd:complexType> <xsd:sequence> <xsd:element name="name"> <xsd:complexType> <xsd:simpleContent> <xsd:extension base="ipv4prefix"> <xsd:attribute name="key" type="xsd:string" fixed="key"/> </xsd:extension> </xsd:simpleContent> </xsd:complexType> </xsd:element> </xsd:sequence> </xsd:complexType> </xsd:element> </xsd:choice> </xsd:sequence> </xsd:complexType> </xsd:element> </xsd:choice> </xsd:sequence> </xsd:complexType> </xsd:element> </xsd:choice> </xsd:sequence> </xsd:complexType> </xsd:element> </xsd:choice> </xsd:sequence> </xsd:complexType> </xsd:schema>
上記のXSD はごく一例*1だが,XML パーサーを使って,これを下のruby コードに変換する. (junoser に含めたパーサージェネレーターで自動生成)
module Junoser class Parser < Parslet::Parser rule(:configuration) do c( b(str("interfaces"), c( interfaces_type ) ) ) end rule(:interfaces_type) do b((arg | str("interface-name")).as(:arg), c( a(str("unit"), arg | arg | str("interface-unit-number"), c( b(str("family"), c( b(str("inet"), c( b(a(str("address"), arg)) ) ) ) ) ) ) ) ) end end end
全体で60万行くらいのXSD が,4万行くらいのPEG パーサーになる.これがjunoser の本体だが,パーサージェネレーターの生成物でもある.
パーサージェネレーターの闇
ここから本題です.ここまでは比較的スッキリと書ける.処理する対象がゴチャゴチャしているため一見ややこしいが,やっていることはシンプル.
問題1: CLI とXSD の微妙なちがい
interfaces { ge-0/0/0 { description "interface 1"; } }
上のようなJUNOS Config がある.よく見ますね.
description
ステートメントの後ろに文字列が来る.
これをdisplay | xml
すると,
<interfaces> <interface> <name>ge-0/0/0</name> <description>interface 1</description> </interface> </interfaces>
なるほど,<description/>
要素に文字列が入っている.
routing-options { autonomous-system 64500; }
いっぽう,上のようなJUNOS Config がある.
autonomous-system
ステートメントの後ろに文字列が来る.
これをdisplay | xml
すると,
<routing-options> <autonomous-system> <as-number>64600</as-number> </autonomous-system> </routing-options>
なるほど,こちらは<autonomous-system/>
要素の中で,新たに<as-number/>
要素で文字列を包んでいて interface description と違う.
では2つのXSD を見てみましょう.
<xsd:complexType name="interfaces-type"> <xsd:sequence> <xsd:choice minOccurs="0" maxOccurs="unbounded"> <xsd:element name="description" minOccurs="0" type="xsd:string"/> </xsd:choice> </xsd:sequence> </xsd:complexType>
<xsd:element name="autonomous-system" minOccurs="0"> <xsd:complexType> <xsd:sequence> <xsd:choice minOccurs="0" maxOccurs="unbounded"> <xsd:element name="as-number" type="xsd:string"/> </xsd:choice> </xsd:sequence> </xsd:complexType> </xsd:element>
ほぼ同じなわけです.「as-number
はただのラベルであって,CLI 上には現れないこと」がXSD からは分からない.
junoser では文脈をくみとりながら「これはただのラベル要素」「これは必要な要素」と選別していて,そこが泥臭いハックになっている.
問題2: そもそもXSD にない定義
junoser はvSRX のXSD をベースにしているため,vSRX にない機能はパースできない.
<xsd:element name="service-set" minOccurs="0" maxOccurs="unbounded"> <xsd:complexType> <xsd:sequence> <xsd:choice minOccurs="0" maxOccurs="unbounded"/> </xsd:sequence> </xsd:complexType> </xsd:element>
たとえば,上はset services service-set ...
に対応するXSD だが,vSRX には定義が存在しない.
問題3: ライセンス
XSD で定義される構文木に泥臭いハックを入れて,CLI にあてはめるためのパーサーを作った.このコードのライセンス,微妙ですよね. いちおうJuniper のかたに確認して公開しているが…大丈夫かな? 詳しいかたがいればアドバイスいただけると嬉しいです.
Juniper はよくてもCisco がNG とかだと,IOS-XR 版が作れなくて困ってしまう.
あと,junoser は個人プロジェクトとして進めているので,Juniper にフィードバックしたくてもできないことがあって困ってる.
おわりに
junoser をメンテするにあたって,けっこう泥臭いことをやっています.でも,けっこう動くレベルのはずです.
もし興味をもってもらえたら ぜひ使ってみてください.パースできないCLI コマンドがあれば,フィードバックよろしくお願いします.自分ではあまり使わないConfig については,問題1 を潰しきれていません.
また,手元にXSD がないのが原因で動かないケース(問題2) もあるので,その際にはXSD の提供に協力してもらえると助かります.
こちらが安定してきたら,IOS-XR 版にも着手予定です.
*1:関係のない要素も省略