YANGを使いこなす! (3) YANG データモデルをどのように使うのかを把握する

# 今回やること
[YANGを使いこなす! Index](https://qiita.com/corestate55/items/70defe12f20f1e282fc0)

[前回](https://qiita.com/corestate55/items/d5c1e1b6457d2aa53217)は、すでに用意されている (出来合いの) config を使って Turing Machine の動かし方をみてみました。今回やることは、その (動かすための) config をどのように作ればよいか、という部分です。これからが本番。今回の内容は YANG Tutorial [InstanceValidation · mbj4668/pyang Wiki](https://github.com/mbj4668/pyang/wiki/InstanceValidation) の内容です。

# 全体像

最初に YANG を中心にしたデータ操作とターゲット(操作対象) との関係について、全体像を見てみましょう。このチュートリアルでは、下図のようなデータ変換/Validation の話を理解するのが目標です。(今回は DSDL Schema でかこったあたり中心。次回は JSON 系の話をやります。)

![YANG Data Conversion/Validation Diagram](https://qiita-image-store.s3.amazonaws.com/0/154230/9a195951-0afd-1831-70af-7d6ae89b8398.png)

# 課題設定

元の YANG Tutorial で使っていた足し算はサンプルとして使ったし、同じものを作っても面白くないので、別な計算(プログラム = 状態遷移表) を作ってみたいと思います。[Turing machine visualization](http://turingmachine.io/) にある "binary increment" が小さくてわかりやすいと思うのでこれを題材に選びましょう

binary increment の状態遷移図

![Binary Incremental State Transition Diagram](https://qiita-image-store.s3.amazonaws.com/0/154230/f588c13d-74d4-8554-c16a-82b7b7601b16.png)

テープには2進数で表現した正の整数値を入れておくことにします。また、上の状態遷移図ではテープ記号として "0", "1" だけではなく "(space)" も設定されているので、`''` (null string) をスペースの代わりに使います。まずは上の状態遷移図を、状態遷移表の形に直します。

| current    |        | Next  |       |       |
|------------|--------|-------|-------|-------|
| State      | Symbol | Write | Move  | State |
| S0 (right) | 1      |       | right | S0    |
|            | 0      |       | right | S0    |
|            | ''     |       | left  | S1    |
| S1 (carry) | 1      | 0     | left  | S1    |
|            | 0      | 1     | left  | S2    |
|            | ''     | 1     | left  | S2    |
| S2 (done)  |        |       |       |       |

<!-- https://www.tablesgenerator.com/markdown_tables -->

これに対して、テープデータを与えます。"binary increment" のお題としては 10 進数で "11" の increment とします。なので、テープデータとしては "11" を 2 進数に直して `1011` を渡してやれば良いことになります。(Initialize request の XML ファイル編集は簡単なので、ここでは主に状態遷移表データの作成に焦点を当てます。)

# Config をつくる

では、上の状態遷移表を Turing Machine が解釈できる XML データで表現しましょう。このとき問題になるのは

* 「Turing Machine が解釈できるデータ」はどんな形式なのか
    * どのようなデータ形式でやりとりすることになっているのか
* 自分で書いたデータが「Turing Machine が解釈できるデータ」になっているかどうか
    * データ形式としての正しさの検証 : Validation

ということですね。順に見ていきます。

## 準備

ここから YANG データの変換等に [pyang](https://github.com/mbj4668/pyang/) やそのほかの XML ツール群を使うので、先にインストールしておきます。

pyang のインストール[^ref_pyang_install]

```
corestate55@ytut:~$ sudo apt install python3-pip
corestate55@ytut:~$ sudo pip3 install pyang
```

[^ref_pyang_install]: 今回は global に pyang インストールしています。venv で動かすのも試してみたんですが、pyang の出力するファイルが include しているパスがずれるので、環境変数を設定して検索パスを設定してやったり、出力されたファイルの include パスを変えてやったりが必要になってきます。

XML tools のインストール (あとででてくる yang2dsdl が裏で呼んでるものとかもあり)

```
corestate55@ytut:~$ sudo apt install libxml2-utils trang xsltproc jing
```

## どのようなデータを作れば良いか?

### データの構造・形式を確認する

YANG で定義しているのは主にこの点で、この操作対象のデータモデル = 操作対象が扱うことのできるデータ形式が定義されています。……といっても、最初いきなり YANG ファイル見ただけではわかりにくいので、いったん tree 表示でデータモデルを表示してみましょう。

```
corestate55@ytut:~/yang-tutorial/data$ pyang --format=tree turing-machine.yang
module: turing-machine
  +--rw turing-machine
     +--ro state                  state-index
     +--ro head-position          cell-index
     +--ro tape
     |  +--ro cell* [coord]
     |     +--ro coord     cell-index
     |     +--ro symbol?   tape-symbol
     +--rw transition-function
        +--rw delta* [label]
           +--rw label     string
           +--rw input
           |  +--rw state     state-index
           |  +--rw symbol    tape-symbol
           +--rw output
              +--rw state?       state-index
              +--rw symbol?      tape-symbol
              +--rw head-move?   head-dir
[...]
```

これは Turing Machine が扱うデータの階層構造です。この tree 表記自体も最近 RFC になっていたのでくわしくは [RFC 8340 - YANG Tree Diagrams](https://tools.ietf.org/html/rfc8340) を参照してください。上の tree の場合、

* `cell*` や `delta*` は配列 (list)
    * tape は複数の cell からできていてcell は位置 (coord) と記号 (symbol) を持つ
    * transition-function は複数の delta からできていて、delta は 入力(input)……現在の状態 (state)、head-position から読んだ cell の記号……に対して、次の状態 (state)・テープに書く記号 (symbol)・ヘッドをどちらに動かすか (left/right) を出力 (output) する
* `rw` になっている項目は client から変更可能なもの
* `state-index` など tag の後についているものはデータの型 (YANGファイル中 `typedef` で定義されている)

といったことがわかります。

### データインスタンスのスケルトンを出す

あとはこうしたデータ構造を XML で表現してやれば良いわけですね。実際この階層構造をそのまま XML にしていくわけです。はい、面倒ですよね。でも大丈夫。これもツールがやってくれます。

```
corestate55@ytut:~/yang-tutorial/data$ pyang -f sample-xml-skeleton turing-machine.yang --sample-xml-skeleton-annotations --sample-xml-skeleton-doctype=config | tee binary-incremental-config.xml
<?xml version='1.0' encoding='UTF-8'?>
<config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
  <turing-machine xmlns="http://example.net/turing-machine">
    <transition-function>
      <delta>
        <!-- # keys: label-->
        <!-- # entries: 0.. -->
        <label>
          <!-- type: string -->
        </label>
        <input>
          <state>
            <!-- type: state-index -->
          </state>
          <symbol>
            <!-- type: tape-symbol -->
          </symbol>
        </input>
        <output>
          <state>
            <!-- type: state-index -->
          </state>
          <symbol>
            <!-- type: tape-symbol -->
          </symbol>
        </output>
      </delta>
    </transition-function>
  </turing-machine>
</config>
corestate55@ytut:~/yang-tutorial/data$
```

いくつかオプションを足しているので、各項目に何を書けばいいのかみたいなことまで入れてくれてます。`doctype=config` 指定したので、設定可能 (`rw`) なデータのテンプレ(スケルトン)だけが出ていますね。

このスケルトンに対して、上の状態遷移表の内容を埋めてみましょう。以降、作ってみる binary incremental config を `binary-incremental-config.xml` というファイル名とします。

## 自分が作ったデータは正しい形式になっているか?

XML を手書きで書くのは難しいですよね。ある程度サイズのある XML を手作業で作るのはなかなかの苦行です。Typo はもちろん、タグを書き間違えていたり、閉じ忘れていたり、といったミスが当然想定されます。であれば、もっと簡単なフォーマットでデータを定義して、プログラムで XML を出力させますか? その場合、作ったプログラムの出力した(大きな) XML が、正しく操作対象に受け入れられる形式になっているかどうかをどうやってチェックしますか?

ということで、次は validation の話です。

### Validation 用の XML schema をつくる

YANG はデータモデルを定義しているので、実際に作ってみた特定のデータ(インスタンス)がその「モデルに沿っているかどうか」を判定するだけの情報 = schema 相当の情報を持っています。が、今回作っているデータインスタンスは XML で表現しているので、YANG そのままだと使えないわけですね。YANG に定義されていることを XML schema 用に出してあげる必要があります。この話は [RFC 6110 - Mapping YANG to Document Schema Definition Languages and Validating NETCONF Content](https://tools.ietf.org/html/rfc6110) にあるってことなんですが、いまは表向きの使い方だけ見ていきます。

まずは、YANG ファイルから XML schema を作りましょう。ここでも `-t` (target) で validate する対象を指定しています。Target `config` は "configuration datastore" です。ここについては  [YANG Tutorial](https://github.com/mbj4668/pyang/wiki/InstanceValidation) に解説があるので参照してほしいのですが、Netconf Message/Operations/Content に対して設定するようになっているというのを見ておいてください。

```
corestate55@ytut:~/yang-tutorial/data$ yang2dsdl -t config turing-machine.yang
== Generating RELAX NG schema './turing-machine-config.rng'
Done.

== Generating Schematron schema './turing-machine-config.sch'
Done.

== Generating DSRL schema './turing-machine-config.dsrl'
Done.
corestate55@ytut:~/yang-tutorial/data$
```

みっつファイルが出力されました。どれも XML schema ですが、用途によって使い分けがあります。
[チュートリアルの "DSDL Schemas"](https://github.com/mbj4668/pyang/wiki/InstanceValidation) を参照してください。

![XML Data Validation Diagram](https://qiita-image-store.s3.amazonaws.com/0/154230/8ebad4ac-5c0c-6fa9-0ed1-da5579bfbcc3.png)
図: https://github.com/mbj4668/pyang/blob/master/doc/tutorial/validation.png


自分が作ったデータに対してそれぞれの schema でチェック (validation) をかけていくことになりますが、yang2dsdl コマンドがそうした処理は一括して面倒を見てくれます。

スケルトンと XML schema ができたので、あとはデータ (XML) を作って schema でチェックします。データの編集は、自分で XML を吐くプログラムを書くとか、エディタを使って直接編集するとかになります。

### 編集時に validate する

手作業で編集する場合、高機能なエディタであればいま編集している XML ファイルを schema をもとに逐次 validate してくれるものがあります。Emacs の nXML-mode もそうした機能を持っているのでそれを試してみましょう。

Emacs nXML-mode が読める XML schema は RELAX NG の compact format と呼ばれている形式なので、これを作ります。

```
corestate55@ytut:~/yang-tutorial/data$ trang -I rng -O  rnc turing-machine-config.rng turing-machine-config.rnc
```

Emacs で編集対象の XML ファイル(出力したスケルトン)を開きます。開いている XML ファイルに対してどの schema ファイルを使うかにはいくつか方法があるみたいですが、`M-x rng-schema-file` して明示的に schema file を指定するのが手っ取り早いかと。いま schema として何を設定しているかは `M-x rng-what-schema` で表示されます。

![Editing Time XML Data Validation with Emacs](https://qiita-image-store.s3.amazonaws.com/0/154230/8da88a59-7f7d-e55c-db87-4ba9bf22f7bd.png)


たとえば、`0` を入力しなければいけないところに `o` を入れてしまったとか、`right`/`left` しか許されていないところに typo して `righr` と入れてしまったとか、validate しておかしいところを指摘してくれます。

### XML ツールで validate する

エディタで validate した場合にはそれでいいかなと思いますが、たとえば自分で書いたプログラムで XML データを自動出力するようなケースでは別途 validate していく必要があります。では、自分が書いたデータをツールで validate してみましょう。

上の図のように入力ミスがある状態でエラーが検出できるかどうか試してみます。

```
corestate55@ytut:~/yang-tutorial/data$ yang2dsdl -s -j -t config -b turing-machine -v turing-machine-config.xml
== Using pre-generated schemas

== Validating grammar and datatypes ...
/home/corestate55/yang-tutorial/data/turing-machine-config.xml:19:27: error: character content of element "state" invalid; must be an integer
/home/corestate55/yang-tutorial/data/turing-machine-config.xml:21:39: error: character content of element "head-move" invalid; must be equal to "left" or "right"
corestate55@ytut:~/yang-tutorial/data$
```

ちゃんとミスが検出できました。これを修正したとしましょう。もう一度チェック。

```
corestate55@ytut:~/yang-tutorial/data$ yang2dsdl -s -j -t config -b turing-machine -v binary-incremental-config.xml
== Using pre-generated schemas

== Validating grammar and datatypes ...
binary-incremental-config.xml validates.

== Adding default values... done.

== Validating semantic constraints ...
No errors found.
corestate55@ytut:~/yang-tutorial/data$
```
これで大丈夫です。

# 実際に動かしてみる

XML Schema で validation できたので、このデータは操作対象 (Turing Machine) が読める形式で書けています。あとは実際に対象に設定してみて、狙った動きになるかどうかの問題です。[^ref_debug]

[^ref_debug]: これについては YANG は何も言ってくれません。YANG が見るのはあくまでも「データモデル」「どのような形式でデータを定義すれば良いか」だけです。Turing Machine の状態遷移の動きから想像がつくと思いますが、状態遷移表のちょっとしたミスで Turing Machine は簡単に無限ループに陥ります。これは「コンパイルは通るがプログラムに問題があって動かすとランタイムエラーが起きる」ような状態で、つまりデバッグが必要だということです。(あるいは、操作対象の実装に問題があって、こちらから送ったデータは正しくても、思ったように動かないかもしれません。)

最後に、binary incremental の回答例を掲載しておきます。

* [yang tutorial data, binary-incremental-config.xml](https://gist.github.com/corestate55/c931596d287da2b1524d66eb83f106f8#file-binary-incremental-config-xml)

できていればこんな感じで動くはずです。( `1011` (11) のインクリメント → `1100` (12))[^ref_overflow]

```
Step State | Tape           | Next Write Move
   1  [S0] | <1>| 0 | 1 | 1 | [S0]         => right/1
   2  [S0] |  1 |<0>| 1 | 1 | [S0]         => right/0
   3  [S0] |  1 | 0 |<1>| 1 | [S0]         => right/1
   4  [S0] |  1 | 0 | 1 |<1>| [S0]         => right/1
tape out-of-range
   5  [S0] |  1 | 0 | 1 | 1 | [S1]        <=  right/null
   6  [S1] |  1 | 0 | 1 |<1>| [S1]     0  <=  carry/1
   7  [S1] |  1 | 0 |<1>| 0 | [S1]     0  <=  carry/1
   8  [S1] |  1 |<0>| 0 | 0 | [S2]     1  <=  carry/0
   9  [S2] | <1>| 1 | 0 | 0 | END
```

[^ref_overflow]: いま Turing Machine 実装は initialize で設定した以上の長さのテープを用意していないので、オーバーフローした場合、サーバ側は死にます。(テープの端、存在しない部分へ新しくセルを追加する処理を実装していないので……。) 「データの正しさ」と実装上の問題は別物という例……(図らずも)