NSO と Network Management Datastore Architecture (NMDA)

この記事はシスコの有志による Cisco Systems Japan Advent Calendar 2019 の 16 日目として投稿しています。

他の記事はこちらからどうぞ。
2017年版: https://qiita.com/advent-calendar/2017/cisco
2018年版: https://qiita.com/advent-calendar/2018/cisco
2019年版: https://qiita.com/advent-calendar/2019/cisco

# はじめに

Cisco NSO は、RFC 6020 (Yang) を忠実に実装した製品です。RFCの記述者が作成した製品であり、逆に NSO の動作を標準化した部分も多くあります。Yangがどの様に使用されるか、Netconfと合わせて使用した際に確認するためのモデルとなるべきもので、自動化の世界では業界をリードするものの一つです。

NSO 5.3 より RFC 8342 Network Management Datastore Architecture (NMDA) を実装しており、この投稿ではその動作について実際の出力と共に説明します。

また、本投稿で全て説明はしませんが、NMDA に関連するRFCは以下です。

- Yang
  - RFC 6020 YANG - A Data Modeling Language for the Network Configuration Protocol (NETCONF)
  - RFC 7950 The YANG 1.1 Data Modeling Language
  - RFC 8342 Network Management Datastore Architecture (NMDA)

- Netconf
  - RFC 6241 Network Configuration Protocol (NETCONF)
  - RFC 8526 NETCONF Extensions to Support the Network Management Datastore Architecture

- Restconf
  - RFC 8040 RESTCONF Protocol
  - RFC 8527 RESTCONF Extensions to Support the Network Management Datastore Architecture

IETF Yang の interfaces と ip management も合わせて更新されました。
その他 IETF model の実装についてです。

- IETF Yang implementation
  - RFC 8343 A YANG Data Model for Interface Management
  - RFC 8344 A YANG Data Model for IP Management
  - RFC 8345 A YANG Data Model for Network Topologies
  - RFC 8346 A YANG Data Model for Layer 3 Topologies
  - RFC 8347 A YANG Data Model for the Virtual Router Redundancy Protocol (VRRP)
  - RFC 8348 A YANG Data Model for Hardware Management
  - RFC 8349 A YANG Data Model for Routing Management (NMDA Version)


# 新しい概念

RFC 8342 は Network Management Datastore Architecture (NMDA) について記述したものです。RFC 6020 (Yang 1.0) の後継である RFC 7950 (Yang 1.1) を前提に作られており、NSO でもこれまで無かった概念が追加されています。Netconf (RFC 6241) で使用するデータストアが増え、使用方法も変更・追加となっています。

RFC 8342 Section 5 では、各データストアがどの様に使用されるかを図示しています。
(これまでのアーキテクチャモデルについては、Section 4 に記述があります)

```
     +-------------+                 +-----------+
     | <candidate> |                 | <startup> |
     |  (ct, rw)   |<---+       +--->| (ct, rw)  |
     +-------------+    |       |    +-----------+
            |           |       |           |
            |         +-----------+         |
            +-------->| <running> |<--------+
                      | (ct, rw)  |
                      +-----------+
                            |
                            |        // configuration transformations,
                            |        // e.g., removal of nodes marked as
                            |        // "inactive", expansion of
                            |        // templates
                            v
                      +------------+
                      | <intended> | // subject to validation
                      | (ct, ro)   |
                      +------------+
                            |        // changes applied, subject to
                            |        // local factors, e.g., missing
                            |        // resources, delays
                            |
       dynamic              |   +-------- learned configuration
       configuration        |   +-------- system configuration
       datastores -----+    |   +-------- default configuration
                       |    |   |
                       v    v   v
                    +---------------+
                    | <operational> | <-- system state
                    | (ct + cf, ro) |
                    +---------------+

     ct = config true; cf = config false
     rw = read-write; ro = read-only
     boxes denote named datastores
```

## RFC 6020/6241 で定義されるデータストア

RFC 6020/6241は Yangを初めて定義したもので、当時すでに存在したネットワーク機器の構造を参考にし、以下のようなデータストアが有ることを想定していました。

- startup
- candidate
- running

startup データベースには「機器起動後に読み込まれる設定」が入っており、running データストア には「使用中の設定」が入っています。startup から読み込まれた設定は、running に設定されます。candidate データストアには「running に使用される候補の設定」がメモリ上に保持されます。この使用方法は、Cisco ルータや他ベンダルータにおいても、長年使用されてきた概念であり、RFC 6020 にも反映されています。

また、保持される設定、またはデータ観点からは、 Configuration データと Operational データがあります。
Configuration データは、機器起動後に読み込むことである一定の状態にするためのものです。ある Configuration データを他機器に設定した場合には、どの機器でも必ず同じ状態となります。

Operational データは機器動作中にメモリ上に作成されるものです。例えばインターフェースの統計情報であったり、機器のVRRPのステート(Master/Backup) は、機器起動後に変更されるものであり、ユーザが設定するものではありません。

Yang上では以下の記述をすることで、モデル内で識別出来ます。

- Configuration データ
  - config true
- Operational データ
  - config false
  
今日の運用では、Configurationデータが、startup/candidate/running データストア上に配置されています。

## RFC 8342 での追加・変更事項

使用されるデータストアは、以下のように増えています。
また、"Operational" という名前がややこしいですが、いわゆる Oper-data (Operational データ, config false) を保存するためだけのデータストアということではありません。

|データストア|Read/Write|Netconf/Restconf での表現|
|------------|---|-------------------------|
|startup     |R/W|&lt;startup&gt;          |
|candidate   |R/W|&lt;candidate&gt;        |
|running     |R/W|&lt;running&gt;          |
|intended    |R  |&lt;intended&gt;         |
|operational state|R  |&lt;operational&gt;      |

### startup/candidate/running データストア

これまで使用されてきた、startup/candidate/running の使用方法に違いはありません。
ただし、設定される内容によっては、機器が使用する最終的なConfigurationではない場合があります。
RFC上では、以下の例が記載されています。

|configurationの種類|意味|
|------------|-------------------------|
|inactive    |configuration として設定されるものの、使用しないという属性がついている。intended datastore には設定されない|
|template-mechanism-oriented|特定の設定がされるが、operational datastore に設定される際には、拡張(configuration transformation)される。Configの一部をテンプレート化し、パラメータを渡すような設定や、Router/Switch上のマクロ設定など。|

テンプレートの例では、それを展開したConfigurationは含まれません。テンプレート設定と、それを展開した内容は同様の内容であり、後者は running configuration には必要ありません。機器にとっては必要かもしれませんが、設定としては冗長なものです。

また、&lt;intended&gt; とこれらデータストアのことをまとめて conventional データストアと呼びます。

### intended データストア

startup/candidate/running に設定された configuration を元に展開された、機器を動作させるために必要なConfigurationです。読み取り専用であり、直接クライアントが編集することは出来ません。running の変更は、即座の intended の変更となります。

また、configuration 内容によっては、running と intended は同じ内容です。intended データストア内のデータは、機器再起動後には再度 running から生成されます。

機器上でマクロなどから拡張して設定されたConfigurationは、クライアントが設定したものではなく(configuration ではない)、Oper-dataに分類せざるを得ないものでした。Ciscoルータも含め、「勝手な」動作をする機器が多く、自動化を妨げてきた要因の一つです。そういったものを running から分離することで、running が本来の意味の「Config」のみを保存するようになります。


### Operational State データストア

機器が動作している状況すべてを表現するデータストアです。例えば intended データストアには、デフォルトデータは含まれませんが、Operational state データストアにはそれが含まれます。

また、oper-data (config false) などの動的に作成される、Configuration 以外のものも含まれますので、コンセプト的には Operational Stateが同じ機器は、全く同じ動きをします。

さらに、例えば BGP RIBの中身などの Leanred configuration、シャーシシリアル番号などの System Configuration も含まれます。

### Config例

#### RFC 8342 Appendix C.2 の例

&lt;running&gt; データストアに設定されているものが以下です。
BGP の設定を行うっているものですが、local-as, peer-as, そして peer のリストが記述されます。

```
<bgp>
  <local-as>64501</local-as>
  <peer-as>64502</peer-as>
  <peer>
    <name>2001:db8::2:3</name>
  </peer>
</bgp>
```

&lt;operational&gt; データストアでは、実際に動作している状態全てが確認出来ます。
また、その設定が running 似ない場合は、どこから設定されたかがわかるよう、origin 属性が設定されます。
/bgp/peer/local-as は、それが設定されていない場合には /bgp/local-as に設定された値を使用するということになっていますので、default が付いています。
/bgp/peer/local-port は、BGPのTCPセッションを作成する際、OSが自動的に決定しますので system が origin となっています。
/bgp/peer/state は、"config false" データであり、どこからか値をコピーしてきた configuration でもありませんので、特に属性は付いていません。

```
<bgp xmlns:or="urn:ietf:params:xml:ns:yang:ietf-origin"
      or:origin="or:intended">
  <local-as>64501</local-as>
  <peer-as>64502</peer-as>
  <peer>
      <name>2001:db8::2:3</name>
      <local-as or:origin="or:default">64501</local-as>
      <peer-as or:origin="or:default">64502</peer-as>
      <local-port or:origin="or:system">60794</local-port>
      <remote-port or:origin="or:default">179</remote-port>
      <state>established</state>
    </peer>
</bgp>
```
running データストアに設定されたあとが変更、intended データストアに反映されたあと、それがが直ちに operational データストアに反映されるわけではありません。BGPのPeerの削除を行う場合、running/intended からの削除は一瞬ですが、システムから削除を行う場合には、実際にはBGP ShutdownやTCPのクローズなど、様々な作業が発生するため、operational データストア上の peer 情報削除は遅れて発生します。

#### RFC 8342 Appendix C.3 の例 (設定の preconfig)

ルータ上の物理インターフェースは、Line cardの追加・削除などによって増減します。&lt;running&gt; に存在しないインターフェースが在ったとしても、当然それは使用できません。&lt;running&gt; に存在しないインターフェースの設定を行うことで、&lt;intended&gt; へ設定の意思を表明出来ます。しかし、&lt;operational&gt; へ反映されることはありません。

Line cardの挿入により、機器はそのインターフェースの設定が可能な状態になったと認識し、&lt;intended&gt; データストア上の設定を有効にしようとします。結果、&lt;operational&gt; にそれが現れてくるため、クライアントは &lt;operational&gt; を確認するだけで、本当に有効な動作状態を確認することが出来ます。

&lt;running&gt; / &lt;intended&gt;

```
<interfaces>
  <interface>
    <name>et-0/0/0</name>
    <description>Test interface</description>
  </interface>
</interfaces>
```
&lt;operational&gt;

```
<interfaces xmlns:or="urn:ietf:params:xml:ns:yang:ietf-origin"
                 or:origin="or:intended">
  <interface>
    <name>et-0/0/0</name>
    <description>Test interface</description>
    <mtu or:origin="or:system">1500</mtu>
  </interface>
</interfaces>
```

## NETCONF

### 新しい operation

NMDA に対応している NETCONF サーバは、新しいOperationを処理することが出来ます。

- &lt;get-data&gt;

  Configurationデータを取得するための &lt;get-config&gt; や "config false" データも取得できる &lt;get&gt; operation がありましたが、&lt;get-data&gt; operation が追加されています。NMDA データストアの指定が出来たり、"config-filter" パラメタによって、"config true"/"config false" ノードのみにフィルタしたりと、柔軟性がありますので、積極的にこちらを使用するべきです。
  
- &lt;edit-data&gt;
  
  &lt;edit-config&gt; operation と同様に使用出来ます。NMDA データストアの指定が可能となっていること、また &lt;edit-config&gt; では、&lt;config&gt; が anyxml ノードでしたが、&lt;edit-config&gt; では anydata へ変更されており、RESTCONF意識したものとなっています。(RESTCONFでJSON 使いたいのに、Payloadは XML でなければいけないのはおかしい）

## RESTCONF

### Datastore resource

RESTCONF URI で使用するリソースに、"ds" が追加されました。これを使用して、どのデータストアに対する要求であるかを指定します。

running データストアは、読み書きを行うために、GET/POST/PUT/PATCH を使用できます。
intended データストアは、読み取り専用のため、GET のみが使用できます。
operational データストアも読み取り専用ですが、action の実行をする場合がありますので、GET/POSTが使用できます。

- http://..../restconf/ds/ietf-datastores:running/
- http://..../restconf/ds/ietf-datastores:intended/
- http://..../restconf/ds/ietf-datastores:operational/


# NSO の NMDA 対応

NSO 5.3 より、NMDA に対応しています。ただし、origin 属性については、現時点で未対応です。
これら機能をNSO上で確認していきましょう。

## NETCONF hello

NSO の Northbound に対して、NETCONF 接続した際、以下のように yang-library 1.1 capability が返答されます。

```
$ netconf-console --hello
<?xml version="1.0" encoding="UTF-8"?>
<hello xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">

//snip//

    <capability>urn:ietf:params:netconf:capability:yang-library:1.0?revision=2019-01-04&amp;module-set-id=3f1f4f98aba52a2290a6a29a1a8d9c0b</capability>
    <capability>urn:ietf:params:netconf:capability:yang-library:1.1?revision=2019-01-04&amp;content-id=3f1f4f98aba52a2290a6a29a1a8d9c0b</capability>
```

## NSO のコンフィグ

以下のようなモデルを作成しました。

```
module test1 {
  namespace "http://com/example/test1";
  prefix test1;

  container server {
    list app {
      key name;
      leaf name {
        type string;
      }
      leaf version {
        config false;
        type string;
      }
    }
  }
}
```
test1 モジュールの中には、server コンテナがあり、各種 app が動作しているイメージです。各アプリケーションのバージョンを設定するための、config false な version leaf があります。

app1, app2, app3, app4 を作成しますが、app2/app3/app4 はinactive としておきましょう。各ノードに、inactive annotation を付与することで表現します。
CLI からは deactivate/activate コマンド、NETCONF/RESTCONF からは attributeで操作できます。

### app1/app2 を CLI から作成する

```
admin@ncs# conf
Entering configuration mode terminal
admin@ncs(config)# server app app1
admin@ncs(config-app-app1)# server app app2
admin@ncs(config-app-app3)# exit
admin@ncs(config)# deactivate server app app2
admin@ncs(config)# commit dry-run outformat xml
result-xml {
    local-node {
        data <server xmlns="http://com/example/test1">
               <app>
                 <name>app1</name>
               </app>
               <app inactive="inactive">
                 <name>app2</name>
               </app>
             </server>
    }
}
admin@ncs(config)#
admin@ncs(config)# commit
Commit complete.
admin@ncs(config)#
```

### app3 を NETCONF から作成する

Payload を作成します。

```
$ cat server-app3.xml
<server xmlns="http://com/example/test1">
  <app inactive="inactive">
    <name>app3</name>
  </app>
</server>
```

NETCONF &lt;edit-data&gt; operation で app3 を作成します。
inactive attribute を付与しています。

```
$ netconf-console --edit-data=server-app3.xml
<?xml version="1.0" encoding="UTF-8"?>
<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="1">
  <ok/>
</rpc-reply>
```

### app4 を RESTCONF から作成する

NETCONFと同様、inactive attribute を付与しています。

```
$ cat server-app4.xml
<app inactive="inactive">
  <name>app4</name>
</app>
$ curl -X POST -u admin:admin -H 'Content-Type: application/yang-data+xml' \
http://localhost:8080/restconf/ds/ietf-datastores:running/test1:server \
-d @server-app4.xml
$
```

### /server/app/version にデータを入れておく

/server/app/version はconfig false な leaf です。本来は何らかのアプリケーションがデータを入れますが、この例では手動でセットします。

```
$ ncs_cmd -o -c 'set /server/app{app1}/version 1.0'
$ ncs_cmd -o -c 'set /server/app{app2}/version 1.0'
$ ncs_cmd -o -c 'set /server/app{app3}/version 1.0'
$ ncs_cmd -o -c 'set /server/app{app4}/version 1.0'
```

### 確認

これで、running データストアには3つのapp が設定されました。
ただし、app2/app3/app4 は inactive です。

```
$ netconf-console --get-data --db running -x /server
<?xml version="1.0" encoding="UTF-8"?>
<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="1">
  <data>
    <server xmlns="http://com/example/test1">
      <app>
        <name>app1</name>
      </app>
      <app inactive="inactive">
        <name>app2</name>
      </app>
      <app inactive="inactive">
        <name>app3</name>
      </app>
      <app inactive="inactive">
        <name>app4</name>
      </app>
    </server>
  </data>
</rpc-reply>
```

intended データストアを確認してみましょう。app2/app3/app4 が表示されないことがわかります。

```
$ netconf-console --get-data --db intended -x /server
<?xml version="1.0" encoding="UTF-8"?>
<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="1">
  <data>
    <server xmlns="http://com/example/test1">
      <app>
        <name>app1</name>
      </app>
    </server>
  </data>
</rpc-reply>
```

次に、operational データストアを確認してみましょう。config false なデータが今度は表示されます。

```
$ netconf-console --get-data --db operational -x /server
<?xml version="1.0" encoding="UTF-8"?>
<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="1">
  <data>
    <server xmlns="http://com/example/test1">
      <app>
        <name>app1</name>
        <version>1.0</version>
      </app>
    </server>
  </data>
</rpc-reply>
```

CLI からは、running と operational データストアを表示することが出来ます。


```
admin@ncs# show running-config server
server app app1
!
! Inactive
server app app2
!
! Inactive
server app app3
!
! Inactive
server app app4
!
admin@ncs#
```

operational データストアを表示するための operational-state container は、ncs.conf 上で /ncs-config/cli/nmda/show-operational-state を true に設定して置かなければ使用できません。

```
admin@ncs# show operational-state server
server app app1
 version 1.0
!
admin@ncs#
```