APRESIA Technical Blog

OpenDaylight Potassiumでプラグイン開発(Notification処理実装編)

はじめに

前回では、DataTreeChangeListenerを使ってDatastoreの更新イベントを契機に様々な処理ができることを確認しました。
今回は、OpenDaylightのコア機能であるMD-SALのNotification処理の実装をしていきたいと思います。

概要



ODLのMD-SALにはNotificationを扱う機構が存在します。これにより、イベントの送受信機能が実装可能となり、例えばモジュール間でのpub/subパターン等を簡単に実装可能になります。
Notificationの定義はYANG で定義可能です。第二章で記載しましたが、 YANG Toolsを利用することで必要なinterfaceやclassが自動生成されます。
今回は、イベント発生契機として前回作成したDataTreeChangeListenerを使います。また、イベント受信(リスナー)では、イベント受信を契機にログ出力する機能を実装していきたいと思います。

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

実装

以下の手順でNotificationの動作を確認します。
  1. YANGモデルの追加
  2. ソースコードの実装
  3. ビルド/ODLで読み込み
  4. CRUD操作をしてNotificationを確認

YANGモデルの追加

Notification をYANGに定義することで、必要なinterfaceなどが自動生成され、使用できるようになります。今回はsample-notifyという名前で、中身はmessageという文字列フィールドが一つ存在するNotificationを定義しました。
module odlsample {
// 省略
    notification sample-notify {
        leaf message {
            type string;
        }
    }
}
YANG定義後ビルドすると以下の配下に、ファイルが自動生成されます。
api/target/generated-sources/BindingJavaFileGenerator/org/opendaylight/yang/gen/v1/urn/opendaylight/params/xml/ns/yangodlsample/rev/240304
自動生成されるファイル

  • SampleNotify.java :Notificationの中身のクラス
  • SampleNotifyBuilder.java:SampleNotifyオブジェクトのビルダ

これらのファイルを利用して実装を進めていきます。

ソースコードの実装

受信処理の実装

受信処理用のファイルを、OdlsampleProvider.javaと同じ階層に作成します。
今回はSampleNotificationListener.javaという名前で作成しました。
Listenerインターフェースの型パラメータとしてSampleNotifyをセットすることで、今回定義したSampleNotifyを受信するリスナーとなります。ListenerインターフェースのonNotificationで、Event受信時の処理内容を定義しています。今回はSampleNotifyのmessageというフィールドの文字列をログに出力する単純な実装になっています。
public final class SampleNotificationListener implements Listener<SampleNotify>, AutoCloseable {
    // 省略
    @Override
    public void onNotification(@NonNull SampleNotify notification) {
        LOG.info("MESSAGE RECIEVED: {}.", notification.getMessage());
    }

リスナーの登録

第二章で説明したDataTreeChangeListenerと同様に、Listenerを登録する必要があります。このListenerはNotificationServiceを使って登録することが出来ます。今回も、Listener自身のコンストラクタで登録処理を実装していきたいと思います。
public final class SampleNotificationListener implements Listener<SampleNotify>, AutoCloseable {
    private final NotificationService notifyService;
    private final Registration registration;
    private static final Logger LOG = LoggerFactory.getLogger(SampleNotificationListener.class);

    public SampleNotificationListener(final NotificationService notifyService) { // --- 1
        this.notifyService = notifyService;
        this.registration = this.notifyService.registerListener(SampleNotify.class, this);  // --- 2
    }
  1. ODLがあらかじめ用意しているNotificationServiceはコンストラクタの引数として渡しており、こちらはインスタンス化する際にblueprint経由で渡されます。
  2. NotificationService ::registerListenerでリスナーの登録ができます。引数として受信するNotificationのクラスとListenerクラス(今回は自分自身となるのでthis)を指定します。

リスナーのインスタンス化

今回、リスナーのクラスとして新たにSampleNotificationListenerを定義しています。現状では定義しただけであり、実際に動作させるためには、定義したクラスをインスタンス化する必要があります。ODLでは、このような場合にblueprintの機能を用いてインスタンス化する仕組みがよく使われます。

blueprintの定義はimpl/src/main/resources/OSGI-INF/blueprint/impl-blueprint.xmlにあります。

今回新たに追加する部分は以下の部分となります。新たに定義したSampleNotificationListenerのインスタンス化処理をxmlで記述することで、モジュール起動時にインスタンス化してくれます。また、先述の通りコンストラクタの引数としてNotificationServiceを渡しています。
  <reference id="notificationService"
    interface="org.opendaylight.mdsal.binding.api.NotificationService" />

  <bean id="listener"
    class="jp.apresia.techblog.impl.SampleNotificationListener"
    init-method="init" destroy-method="close">
    <argument ref="notificationService" />
  </bean>

送信処理の実装

送信のタイミングは自由に決められますが、今回はDataTreeChangeListenerをイベントの契機とするため、該当箇所にコードを追記します。
自動生成されたSampleNotifyBuilderクラスを使用してNotificationの中身を設定します。SampleNotifyBuilder::setMessageで定義したmessageフィールドに文字列をセットすることが出来ます。今回はCRUD操作のmodifiedTypeを文字列のメッセージとして設定しています。
    @Override
    public void onDataTreeChanged(@NonNull Collection<DataTreeModification<Sample>> changes) {
        WriteTransaction wtx = dataBroker.newWriteOnlyTransaction();
        for (DataTreeModification<Sample> change : changes) {
            LOG.trace("change, {}", change);
            final DataObjectModification<Sample> root = change.getRootNode();
            updateData(wtx, root.getModifiedChildren());
            // Notification
            SampleNotify notify = new SampleNotifyBuilder()
                                     .setMessage(root.getModificationType().name())
                                     .build();
            this.pubService.offerNotification(notify);
        }
    }
またthis.pubService.offerNotification(notify);でイベントを送信していますが、このpubService(NotificationPublishService)もblueprintでコンストラクタ引数として渡していることに注意してください。
  <!-- 省略 -->
  <reference id="pubService"
    interface="org.opendaylight.mdsal.binding.api.NotificationPublishService" />

Notificationの確認

実装完了したら、前回同様mvnコマンドでビルドして、ODLで読み込みます。以下のアドレスにアクセスして、CRUD操作がLOGに出力されるか確認していきます。
http://localhost:8181/openapi/explorer/index.html

GETはデータの取得だけで、データツリーの変更はないため、ログには表示されません。
POST/PUT/DELETEでデータを操作すると以下がログとして表示されます。
opendaylight-user@root>log:tail
INFO [DOMNotificationRouter-listeners-0] MESSAGE RECIEVED: SUBTREE_MODIFIED.
INFO [DOMNotificationRouter-listeners-0] MESSAGE RECIEVED: WRITE.
INFO [DOMNotificationRouter-listeners-0] MESSAGE RECIEVED: DELETE.

おわりに

Notificationを利用して、イベント送受信処理の実装が出来ました。また、今回は同一モジュール内で確認しましたが、こちらの機能は、異なるモジュール間でも実現可能です。

今回で4回にわたって連載してきた「OpenDaylight Potassiumでプラグイン開発」シリーズは終了となります。OpenDaylightに関する公開資料は少なく、古いものが多いため、調査が困難でした。 本記事もすぐに古くなってしまうと思いますが、何かの役に立てば幸いです。