SAIとsyncdとsaiplayer

# はじめに

ホワイトボックススイッチ用OSのSONiCですが、Barefoot, Broadcom, Mellanox等さまざまなスイッチベンダーのASICに対応するために、内部ではASICに依存しない共通レイヤーが用意されています。Switch Abstration Interface (SAI)と呼ばれるC言語のAPIです。本稿では、SONiCがどのようにして抽象度の高い指示(e.g. CLI)をSAIにマッピングしているか、どうやってSAIのAPIを呼び出しているか、またその副産物について解説したいと思います。

# SAI

本家はこちら。

https://github.com/opencomputeproject/SAI

URLを見ていただければわかるとおり、Open Compute Project (OCP)によって仕様が策定されています。

どういうものを仕様としてまとめているか大まかに説明すると、スイッチの機能を各社のスイッチASICがそれぞれのSDKで扱える単位まで分解したオブジェクトを定義し、オブジェクトに対しての操作も定義したもの、となります。操作にはオブジェクトの作成、削除、パラメータ設定、パラメータや状態の参照と行ったものがあり、パラメータは属性(attribute)として指定します。

SAIの仕様はOCPによって管理されていますが、実装はそれぞれのスイッチASICベンダーが提供します。ベンダーによってはソースコードが公開されているようですが、ソースコード非公開でSAIのライブラリはバイナリ提供としているベンダーが多いようです。

ふわっとまとめると、SONiCはSAIのAPIを呼び出すことでスイッチASICを制御している、と言えます。

オブジェクトや操作ですが、具体的に並べると下記のようなものです。

- オブジェクト
  - Switch
  - Virtual Router
  - Port
  - Router Interface
  - Nrighbor
  - Nexthop
  - Route entry
  - 等
- 操作
  - create
  - remove
  - set
  - get
  - オブジェクト独自の操作

属性はオブジェクトの種類によって様々で多岐にわたります。たとえば、PortはどのVirtual Routerとつながってるか、ポートスピード、リンク状態、といった属性を持っています。

これらの他、SAIの初期化だったりAPIの呼び出しエントリ取得だったりといった機能が用意されています。

なお、仮想マシン版SONiC (SONiC-VS) では、SAIの実装(`libsaivs`; `src/sonic-sairedis/vslib`)の中身はほとんどなにもしないスタブ実装です。主にユーザープロセス(`bgpd`等)のために用意されるHost Interfaceオブジェクトを作る部分が実装されていて、その他についてはSAIが提供するよりも抽象度の高いレイヤーで処理されています(たとえばIPアドレスを追加する場合、Linuxの`ip address add`コマンドが呼び出されます)。

# おおまかな処理フロー

SONiC内部での設定処理フローは、機能によって多少違いがあるのですがおおよそ下記のようになっています。

1. `config`コマンド等がCONFIG_DBに書き込む
1. `cfgmgr`の各プロセスがCONFIG_DBの内容を読み取り、APPL_DB用の内容に翻訳して書き込む
1. `orchagent`プロセスが、APPL_DBの内容をSAIの機能単位に分解し、ASIC_DBに書き込む
1. `syncd`がASIC_DBの内容を読み取り、SAIを呼び出す
1. SAIの実装の中でASICを制御する

前出の「Linuxコマンドの呼び出し」は主に`cfgmgr`の役割になります。

# CLIからSAIへ

IPアドレス設定を例に、流れを解説します。

## 1.CLIで`config`コマンドを入力

```
sudo config interface ip add Ethernet0 192.168.2.1/24
```

## 2.CONFIG_DBに設定が加わる

`sonic-db-dump -n CONFIG_DB`で見ると下記のような感じになります。

```
  "INTERFACE|Ethernet0|192.168.2.1/24": {
    "type": "hash",
    "value": {
      "NULL": "NULL"
    },
    "ttl": -0.001,
    "expireat": 1670442233.688389
  },
```

## 3.`cfgmgr/intfmgrd`がCONFIG_DBを読んでAPPL_DBに書く

`intfmgrd`は、インタフェース関連の情報を扱うプログラムです。

`sonic-db-dump -n APPL_DB` でみると下記。

```
  "INTF_TABLE:Ethernet0:102.168.2.1/24": {
    "type": "hash",
    "value": {
      "scope": "global",
      "family": "IPv4"
    },
    "ttl": -0.001,
    "expireat": 1670442372.047751
  },
```

## 4.`orchagent`がAPPL_DBを読んでASIC_DBに書く

## 5. `syncd`がASIC_DBから情報を読み取ってSAIコールする

どうもSAIコールのために情報を読み出した後消してしまうようで、設定後にASIC_DBの中をダンプしてもIPアドレス情報は拾えませんでした。

# syncd

`syncd`は、SONiCが動作する上で不可欠なプロセスです。なぜなら、`syncd`はSAIのオブジェクト操作の手続きを一手に引き受けているからです。異常終了時にご丁寧にスイッチオブジェクトを削除するので、`syncd`が止まったらSONiCは動作を停止します。`show services`でsyncd dockerが見当たらなかったら、そのSONiCは正常動作していません。

## syncdのコマンドライン

通常`syncd`は自動的に起動されるので、コマンドラインについて知る必要はないかもしれません。が、指定されているパラメータから読み取れるものがあるかもしれませんので、少し見てみることにします。

`ps`というでみると、下記のようなコマンドラインで呼ばれているのがわかります。

```
/usr/bin/syncd --diag -u -s -p /etc/sai.d/sai.profile -b /tmp/break_before_make_objects
```

パラメータについて見てみます。

- --diag
- -u
- -s
- -p /etc/sai.d/sai.profile
- -b /tmp/break_before_make_objects

ヘルプに解説がありました。

```
admin@sonic:~$ docker exec -it syncd bash
root@sonic:/# syncd --help
Usage: syncd [-d] [-p profile] [-t type] [-u] [-S] [-U] [-C] [-s] [-z mode] [-l] [-g idx] [-x contextConfig] [-b breakConfig] [-h]
    -d --diag
        Enable diagnostic shell
    -p --profile profile
        Provide profile map file
    -t --startType type
        Specify start type (cold|warm|fast|fastfast) 
    -u --useTempView
        Use temporary view between init and apply
    -S --disableExitSleep
        Disable sleep when syncd crashes
    -U --enableUnittests
        Metadata enable unittests
    -C --enableConsistencyCheck
        Enable consisteny check DB vs ASIC after comparison logic
    -s --syncMode
        Enable synchronous mode (depreacated, use -z)
    -z --redisCommunicationMode
        Redis communication mode (redis_async|redis_sync|zmq_sync), default: redis_async
    -l --enableBulk
        Enable SAI Bulk support
    -g --globalContext
        Global context index to load from context config file
    -x --contextConfig
        Context configuration file
    -b --breakConfig
        Comparison logic 'break before make' configuration file
    -h --help
        Print out this message
```

なにやら、Redisを使わずにZMQで情報をやり取りするモードもあるようです。大容量向けでしょうか?

## syncdの内部動作

大雑把に説明すると、`syncd`は「ASIC_DBの更新情報を読み取って、内容のとおりにSAIのAPIをコールする」を繰り返すのが中心的な動作になります。その他としては、定期的に状態参照のSAI APIコールを実行し、得られた情報をSTATE_DBに書き込むという動作があります。

# /var/log/swss/sairedis.rec

では実際にSONiCはどのようなSAI呼び出しをしているのでしょうか。実はそれを確かめる手段が存在します。それが`/var/log/swss/sairedis.rec`です。

実機でも仮想マシンでもかまいません、SONiCを起動させ、ログインできたら`/var/log/swss/sairedis.rec`を開いてみてください。テキストファイルですので、ページャやエディタで開けます。

```
2022-12-02.20:41:06.551657|#|recording on: /var/log/swss/sairedis.rec
2022-12-02.20:41:06.551731|a|INIT_VIEW
2022-12-02.20:41:06.554573|A|SAI_STATUS_SUCCESS
2022-12-02.20:41:06.557623|c|SAI_OBJECT_TYPE_SWITCH:oid:0x21000000000000|SAI_SWITCH_ATTR_INIT_SWITCH=true|SAI_SWITCH_ATTR_FDB_EVENT_NOTIFY=0x559d788ca6a0|SAI_SWITCH_ATTR_PORT_STATE_CHANGE_NOTIFY=0x559d788ca6b0|SAI_SWITCH_ATTR_SWITCH_SHUTDOWN_REQUEST_NOTIFY=0x559d788ca6d0|SAI_SWITCH_ATTR_SRC_MAC_ADDRESS=52:54:00:12:01:00
2022-12-02.20:41:06.660269|g|SAI_OBJECT_TYPE_SWITCH:oid:0x21000000000000|SAI_SWITCH_ATTR_DEFAULT_VIRTUAL_ROUTER_ID=oid:0x0
2022-12-02.20:41:06.661564|G|SAI_STATUS_SUCCESS|SAI_SWITCH_ATTR_DEFAULT_VIRTUAL_ROUTER_ID=oid:0x3000000000004
2022-12-02.20:41:06.662205|c|SAI_OBJECT_TYPE_ROUTER_INTERFACE:oid:0x6000000000081|SAI_ROUTER_INTERFACE_ATTR_VIRTUAL_ROUTER_ID=oid:0x3000000000004|SAI_ROUTER_INTERFACE_ATTR_TYPE=SAI_ROUTER_INTERFACE_TYPE_LOOPBACK|SAI_ROUTER_INTERFACE_ATTR_MTU=9100
2022-12-02.20:41:06.667048|q|attribute_capability|SAI_OBJECT_TYPE_SWITCH:oid:0x21000000000000|OBJECT_TYPE=SAI_OBJECT_TYPE_QUEUE|ATTR_ID=SAI_QUEUE_ATTR_PFC_DLR_INIT
2022-12-02.20:41:06.667409|Q|attribute_capability|SAI_STATUS_SUCCESS|OBJECT_TYPE=SAI_OBJECT_TYPE_QUEUE|ATTR_ID=SAI_QUEUE_ATTR_PFC_DLR_INIT|CREATE_IMP=true|SET_IMP=true|GET_IMP=true
```

このように、`日時|コール種別|オブジェクト種別:オブジェクトID|属性...`という形式で、SAIのAPIコールと応答が記録されています。コール種別はおおよそ下記です。

- `|#|` コメント
- `|c|` create
- `|C|` create (bulk)
- `|s|` set
- `|S|` set (bulk)
- `|g|` get
- `|G|` get responce
- `|r|` remove
- `|R|` remove (bulk)
- `|q|` query
- ~|Q|` query response

たとえば `|c|SAI_OBJECT_TYPE_SWITCH:oid:0x21000000000000|`は、スイッチオブジェクトの作成を意味します。

同じディレクトリに`swss.rec`というAPPL_DBの情報を記録したものもありまして、なんか動かないなというときこのあたりのファイルを見るとASICに設定が投入されてるかを確認できたりします。

# saiplayer

saiplayerは、`sairedis.rec` の内容を読んで、そのとおりにSAIコールを実行するプログラムです。syncd dockerの中にあります。

公式wikiの説明は[こちら](https://github.com/sonic-net/SONiC/wiki/How-to-Use-SAI-Player)。
不具合発生時の緊急回避手段として使われることを想定しているようです。

```shell-session
$ docker exec -it syncd saiplayer --help
Usage: saiplayer [-u] [-i] [-C] [-d] [-s] [-m] [-z mode] [-r] [-p profile] [-x contextConfig] [-h] recordfile

    -u --useTempView:
        Enable temporary view between init and apply

    -i --inspectAsic:
        Inspect ASIC by ASIC DB

    -C --skipNotifySyncd:
        Will not send notify init/apply view to syncd

    -d --enableDebug:
        Enable syslog debug messages

    -s --sleep:
        Sleep after success reply, to notice any switch notifications

    -m --syncMode:
        Enable synchronous mode (depreacated, use -z)

    -z --redisCommunicationMode
        Redis communication mode (redis_async|redis_sync|zmq_sync), default: redis_async

    -r --enableRecording:
        Enable sairedis recording

    -p --profile profile
        Provide profile map file

    -x --contextConfig
        Context configuration file

    -h --help:
        Print out this message
```

「これを使えばテキストでSAIコールを並べて直接制御できるのでは?」と思ったのですが、oidどうすんのよってところがよくわかりません。ひとまず、同様の操作をしているVLANオブジェクトのoidの値をコピペして、試しにVLANを生やそうとしてみます。

```:vlan.rec
2022-12-01.06:14:56.004974|c|SAI_OBJECT_TYPE_VLAN:oid:0x26000000000616|SAI_VLAN_ATTR_VLAN_ID=222
2022-12-01.06:14:56.022152|c|SAI_OBJECT_TYPE_VLAN:oid:0x26000000000617|SAI_VLAN_ATTR_VLAN_ID=444
```

これを`saiplayer`に食わせてみます。

```shell-session
admin@sonic:~$ docker cp vlan.rec syncd:.
admin@sonic:~$ docker exec -it syncd saiplayer vlan.rec
terminate called after throwing an instance of 'std::runtime_error'
  what():  :- translate_local_to_redis: failed to translate local RID oid:0x21000000000000
```

使われてない値ならいけるかなと思って`oid:0x26000000000619`等を試してみましたが、同じエラー。なるほど、動かん。

公式の説明通り、SONiC内部では設定できているつもりなのにASICに設定が入っていないときに、設定を再度ASICに流し込むという用途に限定されたプログラムのようでした。

# おわりに

つらつらと書いてみましたが、「`sairedis.erc`を見るといい」みたいな話はきっとデバッグするときくらいしか役に立たない気がします。

ちょっとしたSONiC内部探検みたいな記事になってしまいましたが、少しでもお楽しみいただければ幸いです。