IOS-XRをgNMIで制御してみる


1. はじめに
2. gNMIってなに？
3. Sample Labの構成
4. 実際にgNMIで設定してみる
5. gNMIで状態を確認してみる
6. おわりに

## はじめに

この記事はシスコの同志による Advent Calendar2020 一枚目の一部として投稿しています

2017年版: https://qiita.com/advent-calendar/2017/cisco
2018年版: https://qiita.com/advent-calendar/2018/cisco
2019年版: https://qiita.com/advent-calendar/2019/cisco 
2020年版: https://qiita.com/advent-calendar/2020/cisco
2020年版(2枚目): https://qiita.com/advent-calendar/2020/cisco2

今年はIOS-XRのProgrammabilityについて少し書いてみようかと思います。

## gNMIってなに？

gNMIはぐぐるとすでに結構な数の記事がヒットしますので、そちらを参照頂いたほうが詳細が記載されていると思いますが、gNMI (gRPC Network Management Interface)は、gRPCをベースとした、ネットワーク機器を管理/操作するためのプロトコルとして定義されています。
gRPCについてはさらに大量の記事がヒットするので、ここでは説明を割愛します。
個人的には今までModel Drivenという文脈ではNetconfと連呼してきましたが、今回はgNMIを紹介させていただきます。

gNMIのInstallは[openconfig](https://github.com/openconfig/gnmi)でも[cisco gNMI](https://github.com/cisco-ie/gnmi)でも他のものでもお好みで良い気がします。(多少差分があってSampleが動かなかったりするかもしれませんが。。。)

YANGはGithubにNative/Open configともに上がっていますが、今回は横着して[Sample Lab](https://github.com/111pontes/xr-pl2)を利用して試してみたいと思います。


## Sample Labの構成
Sample Labの構成を確認すると、gNMIだけではなくAnsibleやNetconf、Model-Driven SDKなどたくさんのシナリオが用意してあって遊びがいがありますが、このうち今回はgNMIだけについて触ります。

```
admin@controller:xr-pl2$ tree -d -L 5
.
├── ansible
│   └── ip_destination_reachable
│       └── library
├── gnmi
├── md-sdk
├── md-sdk+telemetry
├── netconf
└── yang
    └── modules
        └── cisco-ios-xr
            └── 711

11 directories
```

gNMIのDirectoryには以下のようにXR Native Yang modelとOpenconfig Yang modelが用意してあり、IFやISISの設定をするためのJSONが用意されています。
ちょっと遊ぶだけならこれを使い回すだけで十分ですね。

```
admin@controller:gnmi$ ls -al
total 72
drwxrwxr-x 2 admin admin 4096 Feb  5  2020 .
drwxrwxr-x 9 admin admin 4096 Feb  5  2020 ..
lrwxrwxrwx 1 admin admin   65 Feb  5  2020 Cisco-IOS-XR-clns-isis-oper.yang -> ../yang/modules/cisco-ios-xr/711/Cisco-IOS-XR-clns-isis-oper.yang
lrwxrwxrwx 1 admin admin   67 Feb  5  2020 Cisco-IOS-XR-ip-rib-ipv4-oper.yang -> ../yang/modules/cisco-ios-xr/711/Cisco-IOS-XR-ip-rib-ipv4-oper.yang
lrwxrwxrwx 1 admin admin   71 Feb  5  2020 Cisco-IOS-XR-um-if-ip-address-cfg.yang -> ../yang/modules/cisco-ios-xr/711/Cisco-IOS-XR-um-if-ip-address-cfg.yang
lrwxrwxrwx 1 admin admin   67 Feb  5  2020 Cisco-IOS-XR-um-interface-cfg.yang -> ../yang/modules/cisco-ios-xr/711/Cisco-IOS-XR-um-interface-cfg.yang
lrwxrwxrwx 1 admin admin   69 Feb  5  2020 Cisco-IOS-XR-um-router-isis-cfg.yang -> ../yang/modules/cisco-ios-xr/711/Cisco-IOS-XR-um-router-isis-cfg.yang
-rw-rw-r-- 1 admin admin  830 Feb  5  2020 oc-interfaces-gi1-cfg.json
-rw-rw-r-- 1 admin admin  158 Feb  5  2020 oc-interfaces-gi1-enabled-cfg.json
lrwxrwxrwx 1 admin admin   54 Feb  5  2020 openconfig-if-ip.yang -> ../yang/modules/cisco-ios-xr/711/openconfig-if-ip.yang
lrwxrwxrwx 1 admin admin   59 Feb  5  2020 openconfig-interfaces.yang -> ../yang/modules/cisco-ios-xr/711/openconfig-interfaces.yang
-rw-rw-r-- 1 admin admin   81 Feb  5  2020 xr-interfaces-briefs-oper-filter.json
-rw-rw-r-- 1 admin admin  209 Feb  5  2020 xr-interfaces-gi0-shutdown-cfg.json
-rw-rw-r-- 1 admin admin  160 Feb  5  2020 xr-isis-neighbors-oper-filter.json
-rw-rw-r-- 1 admin admin  742 Feb  5  2020 xr-rib-172016255002-oper-filter.json
-rw-rw-r-- 1 admin admin  423 Feb  5  2020 xr-um-interfaces-gi0-cfg.json
-rw-rw-r-- 1 admin admin  173 Feb  5  2020 xr-um-interfaces-gi0-shutdown-cfg.json
-rw-rw-r-- 1 admin admin  410 Feb  5  2020 xr-um-interfaces-lo0-cfg.json
-rw-rw-r-- 1 admin admin 1454 Feb  5  2020 xr-um-interfaces-lo100-vrf-cfg.json
-rw-rw-r-- 1 admin admin 1962 Feb  5  2020 xr-um-isis-cfg.json
```

## 実際にgNMIで設定してみる
XR Native/OpenconfigのIF YANGをpyangで見てみると以下のように見えます。


```Nativeモデル
admin@controller:gnmi$ pyang --format tree --tree-depth 3 Cisco-IOS-XR-um-interface-cfg.yang
module: Cisco-IOS-XR-um-interface-cfg
  +--rw interfaces
     +--rw interface* [interface-name]
     |  +--rw interface-name        xr:Interface-name
     |  +--rw sub-interface-type
     |  |     ...
     |  +--rw ipv4
     |  +--rw ipv6
     |  +--rw dampening!
     |  |     ...
     |  +--rw encapsulation
     |  |     ...
     |  +--rw shutdown!
     |  +--rw mtu?                  uint32
     |  +--rw logging
     |  |     ...
     |  +--rw bandwidth?            uint32
     |  +--rw description?          string
     +--rw interface-preconfigure* [interface-name]
        +--rw interface-name        xr:Interface-name
        +--rw sub-interface-type
        |     ...
        +--rw ipv4
        +--rw ipv6
        +--rw dampening!
        |     ...
        +--rw encapsulation
        |     ...
        +--rw shutdown!
        +--rw mtu?                  uint32
        +--rw logging
        |     ...
        +--rw bandwidth?            uint32
        +--rw description?          string
```

一方Openconfigは以下のように定義されています。

``` Openconfig
admin@controller:gnmi$ pyang --format tree --tree-depth 4 openconfig-interfaces.yang
module: openconfig-interfaces
  +--rw interfaces
     +--rw interface* [name]
        +--rw name             -> ../config/name
        +--rw config
        |  +--rw type           identityref
        |  +--rw mtu?           uint16
        |  +--rw name?          string
        |  +--rw description?   string
        |  +--rw enabled?       boolean
        +--ro state
        |  +--ro type            identityref
        |  +--ro mtu?            uint16
        |  +--ro name?           string
        |  +--ro description?    string
        |  +--ro enabled?        boolean
        |  +--ro ifindex?        uint32
        |  +--ro admin-status    enumeration
        |  +--ro oper-status     enumeration
        |  +--ro last-change?    yang:timeticks
        |  +--ro counters
        |        ...
        +--rw hold-time
        |  +--rw config
        |  |     ...
        |  +--ro state
        |        ...
        +--rw subinterfaces
           +--rw subinterface* [index]
                 ...
```

まずはNative Modelを用いてLooopback 0の設定を入れてみます。

```NativeモデルでLoopback0の設定
admin@controller:gnmi$ more xr-um-interfaces-lo0-cfg.json
{
  "Cisco-IOS-XR-um-interface-cfg:interfaces": {
    "interface": [
      {
        "interface-name": "Loopback0",
        "description": "LOCAL TERMINATION ADDRESS",
        "ipv4": {
          "Cisco-IOS-XR-um-if-ip-address-cfg:addresses": {
            "address": {
              "address": "172.16.255.1",
              "netmask": "255.255.255.255"
            }
          }
        }
      }
    ]
  }
}
admin@controller:gnmi$ gnmi set --verbose --operation update --config xr-um-interfaces-lo0-cfg.json cisco:cisco@198.18.1.11
2020-12-22 04:20:44,000 - gnmi_client.gnmi_client.commands.rpc - INFO - Decoded rpc
2020-12-22 04:20:44,001 - ydk - INFO -
=============== Set Request Sent ================
update {
  path {
    origin: "Cisco-IOS-XR-um-interface-cfg"
    elem {
      name: "interfaces"
    }
  }
  val {
    json_ietf_val: "{\"interface\":[{\"interface-name\":\"Loopback0\",\"description\":\"LOCAL TERMINATION ADDRESS\",\"ipv4\":{\"Cisco-IOS-XR-um-if-ip-address-cfg:addresses\":{\"address\":{\"address\":\"172.16.255.1\",\"netmask\":\"255.255.255.255\"}}}}]}"
  }
}


2020-12-22 04:20:45,275 - ydk - INFO -
============= Set Response Received =============
response {
  path {
    origin: "Cisco-IOS-XR-um-interface-cfg"
    elem {
      name: "interfaces"
    }
  }
  message {
  }
  op: UPDATE
}
message {
}
timestamp: 1579200650765690987


2020-12-22 04:20:45,276 - ydk - INFO - Set Operation Succeeded

```

Router側で見るとちゃんとLo0にDescritption付きでIP addressが設定されています。

```
RP/0/RP0/CPU0:R1#show run int lo 0
Thu Jan 16 18:53:48.583 UTC
interface Loopback0
 description LOCAL TERMINATION ADDRESS
 ipv4 address 172.16.255.1 255.255.255.255
!
```

次にOpenconfigでGi0/0/0/1を設定するためのJSONが用意されているのでOpenconfigも試してみます。

```OpenconfigでGi0/0/0/1の設定
admin@controller:gnmi$ more oc-interfaces-gi1-cfg.json
{
  "openconfig-interfaces:interfaces": {
    "interface": [
      {
        "name": "GigabitEthernet0/0/0/1",
        "config": {
          "name": "GigabitEthernet0/0/0/1",
          "type": "iana-if-type:ethernetCsmacd",
          "description": "CONNECTS TO LSR2 (g0/0/0/1)"
        },
        "subinterfaces": {
          "subinterface": [
            {
              "index": 0,
              "openconfig-if-ip:ipv4": {
                "addresses": {
                  "address": [
                    {
                      "ip": "172.16.1.2",
                      "config": {
                        "ip": "172.16.1.2",
                        "prefix-length": 31
                      }
                    }
                  ]
                }
              }
            }
          ]
        }
      }
    ]
  }
}
admin@controller:gnmi$ gnmi set --verbose --operation update --config oc-interfaces-gi1-cfg.json cisco:cisco@198.18.1.11
2020-12-22 04:30:17,299 - gnmi_client.gnmi_client.commands.rpc - INFO - Decoded rpc
2020-12-22 04:30:17,301 - ydk - INFO -
=============== Set Request Sent ================
update {
  path {
    origin: "openconfig-interfaces"
    elem {
      name: "interfaces"
    }
  }
  val {
    json_ietf_val: "{\"interface\":[{\"name\":\"GigabitEthernet0/0/0/1\",\"config\":{\"name\":\"GigabitEthernet0/0/0/1\",\"type\":\"iana-if-type:ethernetCsmacd\",\"description\":\"CONNECTS TO LSR2 (g0/0/0/1)\"},\"subinterfaces\":{\"subinterface\":[{\"index\":0,\"openconfig-if-ip:ipv4\":{\"addresses\":{\"address\":[{\"ip\":\"172.16.1.2\",\"config\":{\"ip\":\"172.16.1.2\",\"prefix-length\":31}}]}}}]}}]}"
  }
}


2020-12-22 04:30:19,281 - ydk - INFO -
============= Set Response Received =============
response {
  path {
    origin: "openconfig-interfaces"
    elem {
      name: "interfaces"
    }
  }
  message {
  }
  op: UPDATE
}
message {
}
timestamp: 1579201224755628859


2020-12-22 04:30:19,283 - ydk - INFO - Set Operation Succeeded
```

Router側で見るとちゃんとGi0/0/0/1にもDescritption付きでIP addressが設定されています。

```
RP/0/RP0/CPU0:R1#sh run int gigabitEthernet 0/0/0/1
Thu Jan 16 19:01:15.796 UTC
interface GigabitEthernet0/0/0/1
 description CONNECTS TO LSR2 (g0/0/0/1)
 ipv4 address 172.16.1.2 255.255.255.254
!
```

## gNMIで状態を確認してみる
gNMIのRPCではsetだけではなくgetもありますので、gNMIで状態確認もしてみようと思います。
Routing tableを取得するJSONなどこのSampleでは色々用意されていますが、その中でInterfadceを設定したのでinterface briefを取得してみます。

```

admin@controller:gnmi$ more xr-interfaces-briefs-oper-filter.json
{
 "Cisco-IOS-XR-pfi-im-cmd-oper:interfaces": {
  "interface-briefs": {
  }
 }
}
admin@controller:gnmi$ gnmi get --type operational --filter xr-interfaces-briefs-oper-filter.json cisco:cisco@198.18.1.11
{
  "Cisco-IOS-XR-pfi-im-cmd-oper:interfaces": {
    "interface-briefs": {
      "interface-brief": [
        {
          "interface-name": "GigabitEthernet0/0/0/0",
          "interface": "GigabitEthernet0/0/0/0",
          "type": "IFT_GETHERNET",
          "state": "im-state-up",
          "actual-state": "im-state-up",
          "line-state": "im-state-up",
          "actual-line-state": "im-state-up",
          "encapsulation": "ether",
          "encapsulation-type-string": "ARPA",
          "mtu": 1514,
          "sub-interface-mtu-overhead": 0,
          "l2-transport": false,
          "bandwidth": 1000000,
          "bandwidth64-bit": "1000000"
        },
        {
          "interface-name": "GigabitEthernet0/0/0/1",
          "interface": "GigabitEthernet0/0/0/1",
          "type": "IFT_GETHERNET",
          "state": "im-state-up",
          "actual-state": "im-state-up",
          "line-state": "im-state-up",
          "actual-line-state": "im-state-up",
          "encapsulation": "ether",
          "encapsulation-type-string": "ARPA",
          "mtu": 1514,
          "sub-interface-mtu-overhead": 0,
          "l2-transport": false,
          "bandwidth": 1000000,
          "bandwidth64-bit": "1000000"
        },
        {
          "interface-name": "Loopback0",
          "interface": "Loopback0",
          "type": "IFT_LOOPBACK",
          "state": "im-state-up",
          "actual-state": "im-state-up",
          "line-state": "im-state-up",
          "actual-line-state": "im-state-up",
          "encapsulation": "loopback",
          "encapsulation-type-string": "Loopback",
          "mtu": 1500,
          "sub-interface-mtu-overhead": 0,
          "l2-transport": false,
          "bandwidth": 0,
          "bandwidth64-bit": "0"
        },
        {
          "interface-name": "MgmtEth0/RP0/CPU0/0",
          "interface": "MgmtEth0/RP0/CPU0/0",
          "type": "IFT_ETHERNET",
          "state": "im-state-up",
          "actual-state": "im-state-up",
          "line-state": "im-state-up",
          "actual-line-state": "im-state-up",
          "encapsulation": "ether",
          "encapsulation-type-string": "ARPA",
          "mtu": 1514,
          "sub-interface-mtu-overhead": 0,
          "l2-transport": false,
          "bandwidth": 0,
          "bandwidth64-bit": "0"
        },
        {
          "interface-name": "Null0",
          "interface": "Null0",
          "type": "IFT_NULL",
          "state": "im-state-up",
          "actual-state": "im-state-up",
          "line-state": "im-state-up",
          "actual-line-state": "im-state-up",
          "encapsulation": "null",
          "encapsulation-type-string": "Null",
          "mtu": 1500,
          "sub-interface-mtu-overhead": 0,
          "l2-transport": false,
          "bandwidth": 0,
          "bandwidth64-bit": "0"
        }
      ]
    }
  }
}
```
IOS-XRのCLIからshow interface briefを取得するのと同じ情報が無事取得できました。

## 終わりに
今回はXR自体のProgrammabilityについて、gNMIを試してみました。
gRPCはTelemetryでよく使われると思いますが、gNMIを使えば状態の取得だけじゃなくて設定にも使えるのでできることの幅も広がりますね。
個人的にはXMLよりJSONのほうが見やすくてうれしいなあと思ったりします。
このSampleにはgNMI以外にも色々面白いシナリオが揃っているので興味を持った方がいましたらお試しいただければ幸いです。