APRESIA Technical Blog

OpenDaylight Potassiumでプラグイン開発(DataTreeChangeListenerを使ったイベント駆動処理実装編)

はじめに

前回では、Yangファイルを定義するだけでRestconfでのCRUD処理を簡単に実装できることが確認できました。しかしながら、Restconfでデータを更新しても、OPERATIONAL Datastoreにはそのデータが反映されないことがわかりました。OPERATIONAL Datastoreは読み取り専用のデータであり、外部からのRestconfによるデータ変更は許可されていません。OPERATIONAL Datastoreのデータを更新するには、プラグイン内での書き換え処理が必要です。

そこで、今回はRestconfでCONFIGURATION Datastoreが更新された場合に、その情報をOPERATIONAL Datastoreに同期させる処理を、DataTreeChangeListenerという機能を用いて実装していきたいと思います。

本記事のソースコード全体は以下から参照可能です。
https://github.com/t-matz/techblog-odlsample/tree/blog02

実装概要

ODLには、DataTreeChangeListenerという仕組みがあります。これを利用すると、特定のDatastoreの変更を検知して任意の処理をさせることが可能になります。
今回はこちらを利用して、Restconfで更新したCONFIGRATION Datastoreを検知して、OPERATIONAL Datastoreの更新を行います。

実装

DataTreeChangeListenerは、以下の手順で利用します。

  1. DataTreeChangeListener interfaceを実装したclassを作成
  2. onDataTreeChanged methodを実装
    • こちらにデータ更新イベントを受け取った際に実行したい処理を記載します
  3. 実装したclassをDataBrokerに登録

DataTreeChangeListener interfaceを実装

今回は、Archetypesで初期生成されたOdlsampleProvider.javaをDataTreeChangeListenerにします。

public final class OdlsampleProvider implements DataTreeChangeListener<Sample> {
....

Sample classは、監視対象のデータツリーに対応するclassを指定します。
今回は独自データの変更監視をするので、前回作成した独自のYang定義から自動生成されたclassを指定しています。


onDataTreeChanged methodを実装

DataTreeChangeListener interfaceで定義されているonDataTreeChangedを実装します。
特定のDatastoreが更新された際にonDataTreeChangedが呼び出されるため、こちらにデータ更新時に実行したい処理を記載します。

@Override
public void onDataTreeChanged(@NonNull Collection<DataTreeModification<Sample>> changes) {
    WriteTransaction wtx = dataBroker.newWriteOnlyTransaction(); // --- 1
    for (DataTreeModification<Sample> change : changes) {
        LOG.trace("change, {}", change);
        final DataObjectModification<Sample> root = change.getRootNode();
        updateData(wtx, root.getModifiedChildren()); // --- 2
    }
}

  1. データ更新を行うために、書き込み用のトランザクションを生成します
  2. updateDataでOPERATIONAL Datastoreの変更処理を実装しています
updateDataの詳細は割愛しますが、上記で生成したトランザクションを用いて、指定したDatastoreとPathに対して、以下のように実行することでDatastoreへの書き込みが可能です。

wtx.merge(LogicalDatastoreType.OPERATIONAL, SAMPLE_PATH, modifiedSample);
wtx.commit();
今回更新したいDatastoreはOPERATIONALの独自定義データツリー(Sample)のため上記のような指定になっています。

実装したclassをDataBrokerに登録

DataTreeChangeListenerを実装しただけでは、更新イベント検知は出来ません。更新イベントを検知するためには、DataBrokerへListenerを登録する必要があります。
DataBroker::registerDataTreeChangeListenerを呼び出すことでDataBrokerにListenerを登録することが出来ます。
今回は、簡単にするために、Listener自身のコンストラクタで登録処理も実装しています。

public OdlsampleProvider(DataBroker dataBroker) {
        this.dataBroker = dataBroker; // --- 1
        this.registration = this.dataBroker
                .registerDataTreeChangeListener(
                        DataTreeIdentifier.create(LogicalDatastoreType.CONFIGURATION, SAMPLE_PATH), this); // --- 2
    }

  1. コンストラクタの引数からDataBrokerを受け取って、自身のプロパティとします。
    • このDataBroker自体は、blueprint経由で渡しています。
  2. registerDataTreeChangeListenerの引数には、DataTreeIdentifierとListenerを設定します。
    • DataTreeIdentifierで更新検知したいDatastoreとPathを指定します。
    • 今回更新検知したいDatastoreはCONFIGURATIONの独自定義データツリー(Sample)になります
    • Listenerは、自分自身を指定するため this としています
blueprintの定義はimpl/src/main/resources/OSGI-INF/blueprint/impl-blueprint.xmlにあります。
一部抜粋すると、以下のようにODLがあらかじめ用意しているDataBrokerをOdlsampleProviderの引数として指定していることが分かります。

  <reference id="dataBroker"
    interface="org.opendaylight.mdsal.binding.api.DataBroker"
    odl:type="default" />

  <bean id="provider"
    class="jp.apresia.techblog.impl.OdlsampleProvider"
    init-method="init" destroy-method="close">
    <argument ref="dataBroker" />
  </bean>

確認

実装完了したら、前回同様mvnコマンドでビルドして、ODLで読み込みます。
読み込み成功したら、以下のアドレスからRestconf経由で更新します。ユーザー名/パスワードが要求されたら、admin/adminでログインしてください。
http://localhost:8181/openapi/explorer/index.html

  • 適当なデータをPOSTで生成します。
  • configのデータをGETで確認
  • POSTしたデータが取得できることが確認できます。
  • nonconfigのデータをGETで確認
  • POSTしたデータおよびReadOnlyに指定したデータも更新されていることが確認できます。 ここでは記載しませんが、PUTやPATCHでのデータ更新、DELETEでのデータ削除でもconfig/nonconfigともに更新されていることが確認できると思います。

おわりに

今回は、DataTreeChangeListenerを使ってDatastoreの更新イベントを契機に様々な処理ができることを確認しました。実際にODLの様々なプラグインの実装を確認すると、Datastoreを中心にイベントベースでプラグイン同士が疎結合に連携する実装が多く採用されていることがわかると思います。ODLのソースコードを読む場合は、このイメージを持っておくと理解しやすくなるかもしれません。
次回は、Notification機能を使って、pub/subメッセージ処理の実装を試していきたいと思います。