LGTM

Looks Good To Me

JUNOS 17.2 の PEGパーサーで文法チェックする

ネットワーク運用のために、生成した / 手書きしたコンフィグを CIでテストしています。

おおよそ次のような単純なワークフローです。

  1. コンフィグジェネレーターを実行、生成されたコンフィグをGitHub にpush
    • あるいは、手書きしてpush
  2. Travis CI、Jenkins などでテスト
  3. レビュー
  4. マージ & デプロイ

乱暴に「コンフィグをテスト」と言いましたが、ここでは文法チェックを指しています。わざわざパーサー書くほど? 意味あんの? と思ってしまいますが、その理由は後ほど

junoser

PEG パーサー + CI による文法チェックを実現するため、junoser というライブラリーをメンテしています。内部にコンフィグ構文木を持ち、JUNOS を使わず文法チェックしてくれるものです。

github.com

NETCONF(XML RPC) のスキーマからCLI 構文が連想できるという特徴を利用し、NETCONF XSD から構文木を生成しているのですが、この文法のもとになる XSD が JUNOS 17.2 になりましたよ、という話です。

JUNOS 17.2 になって変わった点

変更前は 12.1 でした。古い…

サポートされる文法が大幅に増えた

どれだけ増えたかは表現しづらいのですが、例えばサイズでいうと

Version Lines of XSD MB
12.1 616,701 27
17.2 1,674,019 76

行数、ファイルサイズとも x2.7 くらいの分量です。

より厳密な構文木を作れるようになった

interfaces xe-0/0/0 description foo

たとえばこの文法の例でいえば JUNOS 12.1 では次のような XSD でした。

<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="interface-name">
              <xsd:annotation>
                <xsd:documentation>Interface name</xsd:documentation>
                <xsd:appinfo>
                  <flag>mustquote</flag>
                  <flag>text-choice</flag>
                  <flag>current-product-support</flag>
                </xsd:appinfo>
              </xsd:annotation>
            </xsd:enumeration>
          </xsd:restriction>
        </xsd:simpleContent>
      </xsd:complexType>
    </xsd:element>
    <!-- </name> -->
    <xsd:choice minOccurs="0" maxOccurs="unbounded">
      <xsd:element ref="undocumented" minOccurs="0"/>
      <xsd:element ref="junos:comment" minOccurs="0"/>
        ...
      <xsd:element name="description" minOccurs="0" type="xsd:string">
      </xsd:element>
      <!-- </description> -->
    </xsd:choice>
  </xsd:sequence>
</xsd:complexType>

<xsd:element name="name"> の部分に注目してください。この要素、CLIname というキーワードが必要なのか、不要なのか XSD からは判別不能でした。そのためライブラリ内部で文脈ごとに独自判断し、構文木を生成していました。

「ベタな文法はサポートされているが、マニアックな文法はサポートされない」「パッチがモグラ叩きのよう」だったのはこのためです。

いっぽう、 17.2 では次のような XSD になっています。

<xsd:complexType name="interfaces-type">
  <xsd:sequence>
    <xsd:element name="name">
      <xsd:annotation>
        <xsd:appinfo>
          <flag>identifier</flag>
          <flag>nokeyword</flag>                       <!-- !!! -->
          <flag>current-product-support</flag>
          <identifier/>
        </xsd:appinfo>
      </xsd:annotation>
      <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:annotation>
                <xsd:documentation>Interface name</xsd:documentation>
                <xsd:appinfo>
                  <flag>mustquote</flag>
                  <flag>nokeyword</flag>                <!-- !!! -->
                  <flag>text-choice</flag>
                  <flag>current-product-support</flag>
                </xsd:appinfo>
              </xsd:annotation>
            </xsd:enumeration>
          </xsd:restriction>
        </xsd:simpleContent>
      </xsd:complexType>
    </xsd:element>
    <!-- </name> -->
    <xsd:choice minOccurs="0" maxOccurs="unbounded">
      <xsd:element ref="undocumented" minOccurs="0"/>
      <xsd:element ref="junos:comment" minOccurs="0"/>
      <xsd:element name="description" minOccurs="0" type="xsd:string">
        ...
      </xsd:element>
      <!-- </description> -->
    </xsd:choice>
  </xsd:sequence>
</xsd:complexType>

nokeyword フラグにより「nameCLI キーワードではなく XSD 的なプレースホルダーですよ」ということが判別可能になっています。

なお、16.X の XSD を確認していないため もう少し早い段階で nokeyword フラグが導入されていたかもしれません。

ほんの一例ですが、このようなスキーマ定義の改善によって より厳密な構文木が生成できるようになりました。その結果、モグラ叩き的パッチがマシになると期待できます。

しかしながら、おそらくパッチはゼロにはなりません。 XSD にバグがあるためです。

  • CLI には存在するが、XSD に存在しない文法がある
  • 必要なフラグが記述されていない

など、JUNOS 17.2 ベースのjunoser でもいくつか独自対応しています。サポートされない文法がありましたら、お気軽にコンタクトください。おそらくさくっとパッチできると思います。

遅くなった 😫

JUNOS 12.1 と17.2 で、

  • XSD の行数
  • XSD から生成したjunoser のパフォーマンス (秒間の処理コンフィグ行数)

を比較します。

今回 17.2 に上げると同時に、取得元プラットフォームを vSRX → vMX に変更しました。いくつかの組み合わせでプロットしたものが下のチャートです。

なお、JUNOS 15.1 の XSD は理解できなかったため、そのバージョンのjunoser を生成できていません。

f:id:codeout:20180710220716p:plain

XSD が x2.7 倍になったことにより、残念ながらパフォーマンスは 1/2.7 になっていることが分かります 😢

TODO

さきほど少し触れたように、おそらくXSD のバグによってサポートできていない文法が存在します。モグラ叩きはしばらく続きそうです。Juniper 社にフィードバックできるチャンスがあれば、XSD について報告したいと思っています。

最適化も必要です。たかだか2,000行くらいの設定ファイルに10秒オーダーかかってしまいます。

現在のところruby による実装ですが、go への移植も含めて検討する予定です。

また XSD 行数から想像すると、恐ろしいことに…プラットフォームによって XSD が全く異なるようです。 ユニバーサルなパーサーを生成するためには XSD をマージしないといけない可能性があります。

なぜPEG パーサーで文法チェックをするのか

最後に、後回しにしていた PEG パーサーの必要性について。

junoser の話をすると、割と「それは検証機でやればいいのにw」という反応をされます。

検証機を使った場合、junoser では不可能な文脈のチェック、たとえば「定義されていないポリシーを呼びだしている」ようなことも確認できます。それは素晴らしいことなのですが 残念ながらコストに合わないんですよ、という話をします。

ネットワークエンジニアリングの場合、たとえば実機で単体テストを行い 接続したシミュレーターとルーティングプロトコルを走らせて通信まで確認できたとしても、十分な安心感がありません。標準どおり / 期待通りに動作することはテストできても、通信相手 (場合によっては会社が違う) である特定のハードウェアと思ったように通信できるとは限らないためです。残念なことに それほどデバイスごとの癖に悩まされます。

仮にハードウェアがすべて標準どおりに動き、ベンダー間の相性問題が皆無だとしても AS(自社ネットワーク)全体として想定通りに動作するとは限りません。ネットワークデバイスはルーティングプロトコルによって互いに影響を及ぼし合います。その連鎖のぐあいは、なかなかに想像を超えてきます。

ネットワークの端っこのデバイスをちょこっといじると、通信もしてないし全く関係ないと思われた反対のほうでトラブルが! みたいな予想外の事故にビビりながら運用しています。ネットワークが変にコケると 上に乗っているシステムやサービスを巻き込む可能性が高いため、とても神経質になります。

反対のほうでトラブルが! の「反対のほう」は、場合によっては USの西海岸とかだったり いくつか通信事業者をまたいだ向こうだったりするため、正直「やってみないと分からん」といったことも山ほどあります。

ネットワークエンジニアリングでは、肌感覚として気軽にテストすることが困難です。正確には、気軽にできるテストで得られる安心感は限定的。「ある変更を加えた結果 全体としてどうか」を確かめないと安心を得られない、さらにはあまり事故れないことから、テストセットアップが大規模になり投資コストや人的リソースがかかりがち。テスト自動化はやるべきですが、それによって削減できるのは人的リソースのみです。いずれにせよ投資が必要。

ようやく本題ですが、直接収益を産まないわりに Lab環境は高価です。それを文法チェックごときに使いたくない。しかしながらそれをサボると、限られたメンテナンスウィンドウ中に Typo と戦うハメになります。なので、なるべく凡ミスは安い・カンタン・速い方法で対処するべきだ という思いがあります。

重い問題、たとえばベンダー間の相性やIntra-ASのルーティングは高価な Labテストで拾うしかありませんが、軽い Typo は安く速く潰したい。

こういうモチベーションで junoser をメンテしています。IOS-XR でも同じことをやりたいのですが、それはまた別の話。

お願い

さまざまなJuniper プラットフォーム、バージョンごとのxsd を求めています。相談に乗ってもいいよという方、是非ともご連絡ください!