コンテンツにスキップ

RouteOrch event / notification

概要

orchagentRouteOrch は経路の SAI プログラミング完了時に 2 種類の通知 を送出する。

種別 機構 送信先 目的
ResponsePublisher publishRouteState() APPL_STATE_DB + APPL_DB_ROUTE_TABLE_RESPONSE_CHANNEL fpmsyncd へのプログラミング結果フィードバック
NextHopObserver notifyNextHopChangeObservers() 内部 Observer(NeighOrch, MirrorOrch 等) ルーティング変化のオーケストレーション内通知

関連ページ

データフロー

flowchart LR
  APPDB[("APPL_DB\nROUTE_TABLE")]
  OA["RouteOrch\norchagent"]
  SAI["SAI\nsai_route_api"]
  APPLSTATE[("APPL_STATE_DB\nROUTE_TABLE")]
  RESP["APPL_DB_ROUTE_TABLE\n_RESPONSE_CHANNEL"]
  FPM["fpmsyncd\n(RESPONSE_CHANNEL 購読)"]
  OBS["内部 Observer\n(NeighOrch 等)"]

  APPDB -->|"subscribe"| OA
  OA -->|"sai_route_api"| SAI
  OA -->|"publishRouteState()\n[SET: protocol+err_str]"| APPLSTATE
  OA -->|"publishRouteState()\n[通知]"| RESP
  OA -->|"notifyNextHopChangeObservers()\nNextHopUpdate"| OBS
  RESP --> FPM

1. ResponsePublisher 通知

呼び出し契機

RouteOrch::publishRouteState() は以下のタイミングで呼ばれる1:

状況 行番号
addRoute() 内: SAI エラー時 L923
addRoute() 内: 既存エントリと完全一致(再 publish) L1050
addRoute() 内: 重複エントリ追加スキップ時 L1090
addRoutePost() 末尾: SAI 操作完了後 L2729
removeRoutePost() 末尾: SAI 操作完了後 L2970

送出フィールド

// routeorch.cpp L3185–3202
void RouteOrch::publishRouteState(const RouteBulkContext& ctx, const ReturnCode& status)
{
    std::vector<FieldValueTuple> fvs;

    if (ctx.is_set)
    {
        fvs.emplace_back("protocol", ctx.protocol);
    }

    const bool replace = false;
    m_publisher.publish(APP_ROUTE_TABLE_NAME, ctx.key, fvs, status, replace);
}
フィールド SET 操作時 DEL 操作時
protocol ctx.protocol(空文字列またはプロトコル名) 送信しない(fvs が空)
err_str ResponsePublisher が自動付与 同上(自動付与)

protocol フィールドのデフォルト

RouteBulkContext::protocol の初期値は "":

// routeorch.cpp L157–177: clear() 実装
protocol.clear();  // → ""

APPL_DB の SET メッセージから protocol フィールドを読み取る (L785–788)1:

if (fvField(i) == "protocol" && fvValue(i) != "")
{
    ctx.protocol = fvValue(i);
}
APPL_DB の protocol フィールド APPL_STATE_DB の protocol
"bgp" 等(空でない文字列) そのままコピー
存在しない、または空文字列 "" (空文字列)

err_str フィールドのデフォルト

ResponsePublisher::publish()err_str を自動付与する (response_publisher.cpp L102–103)3:

swss::FieldValueTuple err_str("err_str", PrependedComponent(status) + status.message());
intent_attrs_copy.insert(intent_attrs_copy.begin(), err_str);

PrependedComponent() の決定ロジック (response_publisher.cpp L16–28)3:

std::string PrependedComponent(const ReturnCode &status)
{
    if (status.ok())    return "";
    if (status.isSai()) return "[SAI] ";
    return "[OrchAgent] ";
}
SAI 結果 err_str の値
成功 "SWSS_RC_SUCCESS"
SAI エラー "[SAI] <エラーメッセージ>"
OrchAgent エラー "[OrchAgent] <エラーメッセージ>"

APPL_STATE_DB 書き込み条件

ResponsePublisher は以下の条件でのみ APPL_STATE_DB を更新する (response_publisher.cpp L133–138)3:

操作 SAI 結果 APPL_STATE_DB
SET 成功 protocol + err_str を書き込む
SET 失敗 書き込まない(RESPONSE_CHANNEL 通知のみ)
DEL 成功 エントリを削除(DEL_COMMAND)
DEL 失敗 書き込まない

バッファリングと flush

RouteOrch コンストラクタ (routeorch.cpp L57–58)1:

m_publisher.setBuffered(true);
m_publisher.m_directDbWrite = true;
  • setBuffered(true): 通知は Redis パイプライン経由でバッファリング
  • m_directDbWrite = true: DB 書き込みはパイプライン経由(非スレッド)
  • doTask() の最後に必ず flush() が呼ばれる (routeorch.cpp L1231)1:
/* Flush response publisher so route notifications reach fpmsyncd every batch.
 * Without this, notifications stay buffered in the Redis pipeline until the
 * next batch. */
m_publisher.flush();

2. NextHopObserver 内部通知

NextHopUpdate 構造体

notifyNextHopChangeObservers() が送出する NextHopUpdate 構造体 (routeorch.h L61–68)2:

struct NextHopUpdate
{
    sai_object_id_t vrf_id;
    IpAddress destination;
    IpPrefix prefix;
    NextHopGroupKey nexthopGroup;
};
フィールド 説明
vrf_id sai_object_id_t VRF の SAI オブジェクト ID
destination IpAddress Observer が追跡しているホスト IP アドレス
prefix IpPrefix 現在の最長プレフィックスマッチ
nexthopGroup NextHopGroupKey 新しい nexthop グループキー

nexthopGroup にデフォルト値はなく、常にその時点の実際の nexthop グループキーが設定される。

通知発火条件

// routeorch.cpp L1270
void RouteOrch::notifyNextHopChangeObservers(
    sai_object_id_t vrf_id, const IpPrefix &prefix,
    const NextHopGroupKey &nexthops, bool add)

ADD 時(add=true: - 新規ルートが追加され、そのルートが当該 Observer 宛先の最長プレフィックスマッチになった場合 - 既存ルートの nexthopGroup が変化し、そのルートが最長プレフィックスマッチの場合

DEL 時(add=false: - 削除されたルートが最長プレフィックスマッチであった場合(次の最長マッチを NextHopUpdate で再通知)

attach() 時の即時通知

Observer が attach() した時点で最長プレフィックスマッチが存在すれば即時通知 (routeorch.cpp L340–350)1:

// Trigger next hop change for the first time the observer is attached
auto route = observerEntry->second.routeTable.rbegin();
if (route != observerEntry->second.routeTable.rend())
{
    NextHopUpdate update = { vrf_id, dstAddr, route->first, route->second.nhg_key };
    observer->update(SUBJECT_TYPE_NEXTHOP_CHANGE, static_cast<void *>(&update));
}

デフォルトルートの存在保証

Observer 追跡テーブルには必ずデフォルトルート (0.0.0.0/0 / ::/0) が含まれるため、 最長プレフィックスマッチは常に 1 件以上存在する:

/* Table should not be empty. Default route should always exists. */
assert(!entry.second.routeTable.empty());

主な Observer

Observer 用途
NeighOrch ARP/ND エントリの nexthop 変化追跡
MirrorOrch ミラーセッションの宛先 IP 解決
TunnelDecapOrch トンネル decap 処理の nexthop 解決

コード由来デフォルト詳細 (Phase A)

ResponsePublisher — protocol フィールドのデフォルト: ""

RouteBulkContext::protocol は初期化時に空文字列:

// routeorch.cpp: clear() メソッド
protocol.clear();  // デフォルト: ""

APPL_DB に protocol フィールドが存在する場合のみ上書き (L785–788):

if (fvField(i) == "protocol" && fvValue(i) != "")
{
    ctx.protocol = fvValue(i);
}
  • protocol フィールドが存在しない、または空文字列 → ctx.protocol = ""(空文字列)のまま APPL_STATE_DB に protocol: "" として書き込まれる
  • protocol フィールドが空でない文字列 → その値をそのままコピー

ResponsePublisher — err_str フィールドのデフォルト: "SWSS_RC_SUCCESS"

SAI 成功時は PrependedComponent(status)"" を返し、status.message()"SWSS_RC_SUCCESS" になる:

// response_publisher.cpp L102
swss::FieldValueTuple err_str("err_str", PrependedComponent(status) + status.message());
  • 成功時の err_str 値: "SWSS_RC_SUCCESS"(プレフィックスなし)
  • SAI エラー時: "[SAI] " + エラーメッセージ
  • OrchAgent エラー時: "[OrchAgent] " + エラーメッセージ

ResponsePublisher — flush タイミング

doTask() の末尾で必ず flush() が呼ばれるため、バッチ処理完了後に全通知がまとめて送出される。個別 route ごとに flush は 行われない

NextHopObserver — NextHopUpdate のデフォルト値

NextHopUpdate 構造体のフィールドはすべて呼び出し時の実際の値で埋められる。構造体自体にデフォルト値は定義されていない:

NextHopUpdate update = { vrf_id, entry.first.second, prefix, nexthops };
  • nexthopGroup が空(nexthop なし)の状態で通知されるのは、削除時に次の最長マッチが blackhole ルートのみの場合。

制約

  • publishRouteState() は SET 操作時のみ protocol を送信する。DEL 操作時は fvs が空のため APPL_STATE_DB からエントリが削除される。
  • SAI プログラミング失敗時は APPL_STATE_DB への書き込みは行われないが、RESPONSE_CHANNEL への通知は行われる。
  • notifyNextHopChangeObservers()最長プレフィックスマッチが変化した場合のみ 通知を発火する。最長マッチが同じルートで nexthopGroup も同じ場合は通知しない。
  • Observer は attach() で登録した IP アドレスを含む最長プレフィックスマッチの変化のみを受け取る。

購読者 (consumer)

ResponsePublisher 通知

プロセス 参照 用途
fpmsyncd APPL_DB_ROUTE_TABLE_RESPONSE_CHANNEL SAI プログラミング結果を FRR へフィードバック
route_check.py APPL_STATE_DB ROUTE_TABLE APPL_DB と APPL_STATE_DB の整合確認

NextHopObserver 通知

Observer attach() 箇所
NeighOrch ネイバー解決時
MirrorOrch ミラーセッション設定時

関連リファレンス

引用元

運用ヒント

確認コマンド

# APPL_STATE_DB の経路プログラミング結果確認
sonic-db-cli APPL_STATE_DB hgetall 'ROUTE_TABLE:10.0.0.0/24'

# err_str でエラー経路を検索
sonic-db-cli APPL_STATE_DB keys 'ROUTE_TABLE:*' | while read k; do
  err=$(sonic-db-cli APPL_STATE_DB hget "$k" err_str)
  [ "$err" != "SWSS_RC_SUCCESS" ] && echo "$k: $err"
done

# RESPONSE_CHANNEL の通知を監視(デバッグ用)
redis-cli -n 0 subscribe APPL_DB_ROUTE_TABLE_RESPONSE_CHANNEL

典型エントリ例

# APPL_STATE_DB: BGP 経路のプログラミング成功
APPL_STATE_DB ROUTE_TABLE:10.1.0.0/24
  err_str: SWSS_RC_SUCCESS
  protocol: bgp

# APPL_STATE_DB: protocol フィールドなし経路(static など fpmsyncd 経由以外)
APPL_STATE_DB ROUTE_TABLE:192.168.1.0/24
  err_str: SWSS_RC_SUCCESS
  protocol: (空文字列)

よくある問題

  • err_str"[SAI] ..." → SAI プログラミング失敗。/var/log/syslog の orchagent ログで詳細を確認する。
  • APPL_STATE_DB にエントリが存在しない → SAI 失敗または DEL 操作後。RESPONSE_CHANNEL のログを確認する。
  • protocol が空文字列 → APPL_DB の ROUTE_TABLE エントリに protocol フィールドが存在しない(静的経路や一部の直接書き込みツールで発生する)。