Watson IoT Platform Data Store Connectorをもう少し使い込んでみる

Watson IoT Platformの提供するData Store Connector (以下DSC)はIoTデバイスから収集した情報を外部データベースに自動的に集積できるという優れものです。

IBMが提供しているApplication SDKとサンプルコードを使用すると簡単に設定できますが、実践的な運用にはやや情報不足な感もあり自分なりに調べた結果をまとめておきます。

# 1 環境
Python 3.7
wiotp-sdk 0.11.0 (pip3で導入可能）

# 2 準備とサンプルコードの実行
## 2.1 前提
今回はDSCの接続先としてCloudantを使用し、以下の作業は前提として終了しているものとします。
- IBM Cloudのアカウントを作成する
- Watson IoT Platformのサービスを作成し実行する
- Cloudantのサービスを作成実行する

作業方法について詳しい書き物はたくさんありますので、ここでは割愛します。
Qiitaにもたくさんありますので検索してみましょう。

## 2.2 Watson IoT Platformの認証キーとトークンを生成
次にWatson IoT Platformの認証キーとトークンを生成します。
これはこれから実行するコードがwiotp-sdkを使用してWatson IoT Platformの提供するAPIを呼び出す際の認証に使用されるものです。

詳細については以下を参照してください。
https://developer.ibm.com/jp/tutorials/iot-generate-apikey-apitoken/

特にトークンについては生成時にしか表示されないので、画面に印字された文字列を忘れないようにメモしておきましょう。

## 2.3 Cloudantサービス資格情報 
以下の手順でCloudantのサービス資格情報のうちusernameとpasswordを確認しておきます。
https://cloud.ibm.com/docs/Cloudant?topic=Cloudant-creating-an-ibm-cloudant-instance-on-ibm-cloud&locale=ja#locating-your-service-credentials

注）最近のCloudantな二種類の認証方式を提供しているようですが、サービスのプロビジョニンング（作成）のときに「Use both legacy credentials and IAM」を選択しておかないと本稿で扱うコードの実行はうまくいかないです。

ここで確認されたusernameとpasswordはApplication SDKのService Bindingを通してWatson IoT Platformに渡され、Watson IoT PlatformがCloudantにデータを書き込む際の認証に使用されます。

## 2.4 サンプルコードの実行
以下のリンク先にCloudantへ接続するためのDSCをセットアップするサンプルコードが記載されています。

https://www.ibm.com/support/knowledgecenter/SSQP8H/iot/platform/reference/dsc/cloudant.html

serviceBinding内のusernameとpasswordを2.3で確認したものに書き換えて実行します。

``` bash
# export WIOTP_AUTH_KEY='kkkkkkkkkkk'
# export WIOTP_AUTH_TOKEN='tttttttttttttttt'
# python sample.py
```

上記実行例では2.2で生成した認証キーとトークンを使用してサンプルコード(sample.py)を実行しています。
サンプルコード実行前に認証キーとトークンをそれぞれWIOTP_AUTH_KEYとWIOTP_AUTH_TOKENにセットします。

# 3 もう少し使い込む
2.4のようにサンプルコードを実行することで、Watson IoT Platformに報告されたイベントは全てCloudant内のDBに保存されるようになります。

ここでサンプルコードの内容をもう少しよく読んでみると、

- Service Bindingを生成する
- Service Bindingに紐つけてConnectorを生成する
- ConnectorのプロパティとしてDestinationとRuleを生成する

のように何もない状態からいくつかのDSCに必要なオブジェクトを生成していることが分かります。

スクラッチに状態から一応動作するところまで持っていってくれますが、実運用を前提に使いこなしていくためには現在の構成を確認したり変更したりする手順が必要になりそうです。

以下、取り敢えずDSCを使いこなしていくために最低限必要そうな手順をいくつか試していきます。


## 3.1 諸々のオブジェクトの一覧表示
現状どのような設定になっているのか把握するのは運用上必須です。
Service BindingsとDSCの一覧を表示させるプログラムを書いてみました。

ApplicationClient.ServiceBindingsやApplicationClient.dscなどはiteratorでループしていますが、通常のlistとは使い勝手が違うので注意が必要です。例えばlen()を使用していくつ格納されているのか確認しようとしても0が返されてしまいます。

``` python:ls_dsc.py
import wiotp.sdk.application

options = wiotp.sdk.application.parseEnvVars()
appClient = wiotp.sdk.application.ApplicationClient(options)

print("Service Bindings")
for s in appClient.serviceBindings:
    print("ID: " + s.id)

print("")
print("DSC")
for c in appClient.dsc:
    print("ID: " + c.id)
    print("serviceId: " + c.serviceId)
    for d in c.destinations:
        print("Destination.name: " + d.name)
    for r in c.rules:
	print("Rules.id: " + r.id)
        print("Rules.name: " + r.name)
        print("Rules.destinatioName: " + r.destinationName)
        print("Rules.typeId: " + r.typeId)
        print("Rules.eventId: " + r.eventId)
```

以下、私の環境で実行した結果です。

``` bash
# export WIOTP_AUTH_KEY='kkkkkkkkkkkkkkkkkkk'
# export WIOTP_AUTH_TOKEN='tttttttttttttttttt'
# python ls_dsc.py
Service Bindings
ID: d2b141eb-2390-4fa0-b253-24b14c465298
ID: 5aa553e6-692c-45fd-ba2c-a717a635d5bb
ID: 85cb36b6-eda7-48b0-bc5a-6eb2c2f03619
ID: ae5a431d-98f6-4c9b-9ffc-a3ba9206770e
ID: 4f905b29-70ce-4f01-a2c6-9feb1c2d3e4a
ID: 137becb6-502a-4919-83da-079a434a7175

DSC
ID: 5db2d160f7e0960025221b51
serviceId: 4f905b29-70ce-4f01-a2c6-9feb1c2d3e4a
Destination.name: events
ID: 5db2d1b8f7e0960025221b52
serviceId: 137becb6-502a-4919-83da-079a434a7175
Destination.name: events
Rules.id: 5db2d1bbf7e0960025221b53
Rules.name: allevents
Rules.destinatioName: events
Rules.typeId: *
Rules.eventId: *
```

2.4のサンプルコードを修正してイベント通知のみをCloudantに流すようにしたのですが、途中に試行錯誤を挟んだ結果不要なService BindingやDSCが残っています。

DSCは二つありますが、一番目にリストされているもの（ID=5db2d160f7e0960025221b51）はRulesが空のようで機能しないものです。Ruleを作成する前にサンプルコードが異常終了してしまったために不完全な状態で残ったものです。
もう一つの方（ID=5db2d1b8f7e0960025221b52）はDestination/Rulesともに作成済みで機能するものです。

Service Bindingsは６個の作成されていますが、有効なDSCが使用しているのはdsc.serviceIdでポイントされているもの（ID=137becb6-502a-4919-83da-079a434a7175）で、残りの５個はやはり不要なものです。

このls_dsc.pyを使えばDSCの設定状況の把握が可能になると思います。現状は最低限のプロパティのみ表示させていますが、必要に応じて表示するプロパティを追加させていけば良いと思います。

表示が可能になったので、次に不要なオブジェクトの削除をしていきます。

## 3.2 不要なオブジェクトの削除
Python SDKを使って不要なオブジェクトの削除ができる方法を探したのですが見つかりませんでした。
REST APIでdeleteすればOKなので直接curlを使用して削除していくことにします。

DSCで使用しているService Bindingは削除できないようなので、先に不要なDSCを削除してから次に不要なService Bindingを削除します。

REST APIの詳細については以下のリンクを参照してください。
https://docs.internetofthings.ibmcloud.com/apis/swagger/v0002/historian-connector.html#/Services

認証に使用しているusername/passwordは2.2で作成した認証キーとトークンを指定します。REST APIで指定しているURL内のooooooはWatson IoT Platformのorganization idに置き換えます。

``` bash
# curl -X DELETE --user 'kkkkkkkkkkkkkkkkkkk:tttttttttttttttttt' https://oooooo.internetofthings.ibmcloud.com/api/v0002/historianconnectors/5db2d160f7e0960025221b51
# curl -X DELETE --user 'kkkkkkkkkkkkkkkkkkk:tttttttttttttttttt' https://oooooo.internetofthings.ibmcloud.com/api/v0002/s2s/services/5aa553e6-692c-45fd-ba2c-a717a635d5bb
# curl -X DELETE --user 'kkkkkkkkkkkkkkkkkkk:tttttttttttttttttt' https://oooooo.internetofthings.ibmcloud.com/api/v0002/s2s/services/d2b141eb-2390-4fa0-b253-24b14c4652
# curl -X DELETE --user 'kkkkkkkkkkkkkkkkkkk:tttttttttttttttttt' https://oooooo.internetofthings.ibmcloud.com/api/v0002/s2s/services/85cb36b6-eda7-48b0-bc5a-6eb2c2f03619
# curl -X DELETE --user 'kkkkkkkkkkkkkkkkkkk:tttttttttttttttttt' https://oooooo.internetofthings.ibmcloud.com/api/v0002/s2s/services/ae5a431d-98f6-4c9b-9ffc-a3ba9206770e
# curl -X DELETE --user 'kkkkkkkkkkkkkkkkkkk:tttttttttttttttttt' https://oooooo.internetofthings.ibmcloud.com/api/v0002/s2s/services/4f905b29-70ce-4f01-a2c6-9feb1c2d3e4a
# export WIOTP_AUTH_KEY='kkkkkkkkkkkkkkkkkkk'
# export WIOTP_AUTH_TOKEN='tttttttttttttttttt'
# python ls_dsc.py
Service Bindings
ID: 137becb6-502a-4919-83da-079a434a7175

DSC
ID: 5db2d1b8f7e0960025221b52
serviceId: 137becb6-502a-4919-83da-079a434a7175
Destination.name: events
Rules.id: 5db2d1bbf7e0960025221b53
Rules.name: allevents
Rules.destinatioName: events
Rules.typeId: *
Rules.eventId: *
```

削除後に再度ls_dsc.pyを実行して結果を確認しました。
不要なService BindingやDSCが削除されスッキリしました。

Service BindingやDSCの削除が可能なことが分かりましたので、諸設定の変更も”削除”して”新規作成”すれば可能です。DSCの導入・設定に必要最低限な機能は確認できましたが、毎回”削除”して”新規作成”も大変そうなので構成のアップデートの方法も確認していきます。

## 3.3 構成のアップデート
実装初期の試行錯誤や仕様変更などによって一旦生成したDSCの構成を変更したくなることは多いと思われます。
Service BindingやDSC、DSCに内包されるDestinationsやRulesを生成したときに指定された各種プロパティはupdate()メソッドを使用することで変更できるようになっていそうです。

現時点では仕様が記述された文書は見つけられていません。あくまでソースコードを読んでみて分かったことです。
以下に一例としてRuleを変更するコードを記載しますが、今後のPython SDKの変更で動作しなくなる可能性もありますのでご注意ください。

``` python:update_rule.py
import wiotp.sdk.application

options = wiotp.sdk.application.parseEnvVars()
appClient = wiotp.sdk.application.ApplicationClient(options)

# Get connector instance with its ID (=5db2d1b8f7e0960025221b52) as a key
c = appClient.dsc['5db2d1b8f7e0960025221b52']

# Update rule (ID=5db2d1bbf7e0960025221b53)
s = {
    'deviceType' : '*',
    'eventId' : 'bar'
}
c.rules.update(ruleId='5db2d1bbf7e0960025221b53', ruleType='event', name="allevents", destinationName="events", description="A sample rule", enabled=True, selector=s)
```

上記の例ではeventIdのみを'*'から'bar'に変更しています。

appClient.dscに対してDSCのID（今回の例では5db2d1b8f7e0960025221b52）をキーに指定してIDに対応するDSCのインスタンスを取得します。このインスタンスのrulesに対してupdate()を呼び出すことでRuleの更新が可能です。
このupdate()メソッドの第一引数に更新したいRuleのIDを指定して、残りに更新したいRuleのプロパティをセットします。更新しないプロパティも現状の値をセットして呼び出す仕様のようです。
selector引数の指定がやや難しいですが、ソースコードやPython SDKが呼び出しているREST APIの仕様を読むとどうもこのようにセットするようです。

以下、実行結果です。

``` bash
# export WIOTP_AUTH_KEY='kkkkkkkkkkkkkkkkkkk'
# export WIOTP_AUTH_TOKEN='tttttttttttttttttt'
# python ls_dsc.py
Service Bindings
ID: 137becb6-502a-4919-83da-079a434a7175

DSC
ID: 5db2d1b8f7e0960025221b52 <- !!!DSCのID
serviceId: 137becb6-502a-4919-83da-079a434a7175
Destination.name: events
Rules.id: 5db2d1bbf7e0960025221b53　<- !!!RuleのID
Rules.name: allevents
Rules.destinatioName: events
Rules.typeId: *
Rules.eventId: * <- !!!変更前
# python update_rule.py　<- !!!変更
# python ls_dsc.py
Service Bindings
ID: 137becb6-502a-4919-83da-079a434a7175

DSC
ID: 5db2d1b8f7e0960025221b52
serviceId: 137becb6-502a-4919-83da-079a434a7175
Destination.name: events
Rules.id: 5db2d1bbf7e0960025221b53
Rules.name: allevents
Rules.destinatioName: events
Rules.typeId: *
Rules.eventId: bar <- !!!変更後
```

# 4 まとめ
Python SDKを使用してDSCの設定を確認・削除・変更する方法を紹介しました。

繰り返しになりますがIBMが明確にガイドしていない方法も含まれており、本稿で例示したコードの使用は全て自己責任ということでお願いします。また今後のPython SDKの実装の変更によってこれらのコードが動かなくなる可能性もありますのでご注意ください。

確実なのはREST APIを使用して実装することなのでしょうけれども、Python SDKを使用する方がコードもシンプルですし簡単です。今後Python SDKがより使いやすい形へと進化していってくれることを期待しています。

なおREST APIを使用して設定する方法については以下のQiitaの記事が参考になると思われますので、興味のある方は参照してみてください。
https://qiita.com/Motonaga/items/6304f5f66f63cb566943