[OCI] Functions を使用して、Object StorageにUploadされたCSVファイルをJSON形式でAutonomous DBにロードしてみた。

Object StorageにCSVファイルをUploadされた際にイベント・サービスがOracle Functionsをキックし、Autonomous Databaseにロードするサンプルを動かしてみた。
Autonomous DatabaseへのロードはRESTを呼び出しSODAを使用してJSON形式で格納します。パスワードの管理にはOCIシークレットを使用し、FunctionはPythonで記載しています。

※ Oracle Functionsとは、Oracle Cloud Infrastructureで提供されるFn Projectのマネージドサービス
※ OCIシークレットとは、パスワードなどリソースにアクセスするために使用する資格情報を安全に一元的に管理できます。

# 作業ステップ

- Functionsの準備
- Autonomous DBの作成
    - コレクションの作成
- Object Storageの準備
- 動的グループの作成とポリシーの割り当て
- シークレットの作成
- Functionsの作成
- イベント・ルールの作成

## Functionsの準備
Oracle Functionsが利用できるように以下の作業を実施

- 仮想クラウド・ネットワークの作成
- IAMポリシーの作成
- (オプション) OCIレジストリの作成
- [Cloud Shellを使ってOracle Functionsサンプルを動かしてみた。](https://qiita.com/kenwatan/items/d8fd2426e93a1cfb65f6) を参考に fn shell のセットアップを実施。

## Autonomous Databaseの作成
接続するAutonomous Databaseを作成
作成したAutonomous Databaseの[サービス・コンソール]を開き、[開発]からRESTfulサービスとSODA のBase URLをコピー
![図1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108635/c452ba3f-e22d-3e5b-4a58-9e55073d515d.jpeg)

#### コレクションの作成
作成したAutonomousDBに対して、ロード用のコレクション(regionsnumbers)を作成します。
今回は、adminスキーマ（デフォルトでRESTアクセスが有効になっている）を使用します。
※ 本番環境では、別のスキーマを作成してRESTサービスを有効にすることを推奨
   方法の詳細は、[マニュアル](https://docs.oracle.com/en/database/oracle/oracle-rest-data-services/19.1/index.html) を確認
ターミナルから、curlコマンドを発行しコレクション 'regionsnumbers' を作成します。

```
curl -X PUT -u 'ADMIN:<DB password>' "<ADW-ORDS-URL>admin/soda/latest/regionsnumbers“

例） curl -X PUT -u 'ADMIN:<DB password>' "https://AAAAA-ORCLATP.adb.ap-tokyo-1.oraclecloudapps.com/ords/admin/soda/latest/regionsnumbers"
```
## Object Storage の準備
Object Storageに2つのバケットを作成
1つ目のバケットは、Autonomous DatabaseにロードするCSVファイルの配置
2つ目のバケットは、処理が完了したファイルの移動場所
例えば「input-bucket」と「processed-bucket」を作成
1つ目のバケット（入力用）の「オブジェクト・イベントの出力」ボックスにチェックを入れます。
![図2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108635/4fe39e05-b42c-ecb0-aeff-8061fd7bbab8.jpeg)

## 動的グループの作成とポリシーの割り当て
Functionから他のOCIサービスが利用できるようにポリシーを割り当てる動的グループを作成します。

コンパートメント内のすべてのファンクションをマッチング・ルールに指定する動的グループの例

```
ALL {resource.type = 'fnfunc', resource.compartment.id = 'ocid1.compartment.oc1..aaaaaxxxxx'}
```

動的グループが 2 つのバケット内のオブジェクトを管理できるように新しいポリシーを作成

```
Allow dynamic-group <dynamic-group-name> to manage objects in compartment <compartment-name> where target.bucket.name=‘<input-bucket-name>’
Allow dynamic-group <dynamic-group-name> to manage objects in compartment <compartment-name> where target.bucket.name=‘<processed-bucket-name>’
```

シークレットを読み取れるようにポリシーを割り当て

```
Allow dynamic-group <dynamic-group-name> to read secret-family in compartment <compartment-name>

allow service objectstorage-<region-name> to manage object-family in compartment <compartment-name>
	例）allow service objectstorage-us-ashburn-1 to manage object-family in compartment <compartment-name>
```

## シークレットの作成
シークレット操作用のサービスレベルポリシーの割り当て

```
allow service VaultSecret to use vaults in tenancy
allow service VaultSecret to use keys in tenancy
```

Autonomous Databaseへの接続パスワードを格納するシークレット作成します。

1. ボールトの作成
2. キーの作成
3. DBユーザのパスワードのシークレットを作成

#### ボールトの作成
OCI Webコンソールから[セキュリティ] > [ボールト] で「ボールトの作成」を選択
![図3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108635/202142d1-11f3-2b55-c2f9-af99b180c729.png)


#### キーの作成
作成したボールトに対して「キーの作成」からキーを作成します。
![図4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108635/c8c9b60b-22d3-dd09-a149-3703e7652c5d.png)


#### DBユーザのパスワードのシークレットを作成
![図5.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108635/f4cfd6f4-001e-1bcd-697f-3beea2dd46f5.jpeg)

- 名前・説明を入力
- 暗号化キー：作成したキー
- シークレット・タイプ：プレーンテキスト
- シークレット・コンテンツ：データベースユーザのパスワード

作成したシークレットのOCIDを記録します。

## Functionsの作成
すべてのファイルは [Github](https://github.com/kenwatan/functions_Load_ADB_python_Secrets)より取得可能です。

シークレット取得部分

```
import oci
import base64
import sys

def read_secret_value(secret_client, secret_id):
    response = secret_client.get_secret_bundle(secret_id)
    base64_Secret_content = response.data.secret_bundle_content.content
    base64_secret_bytes = base64_Secret_content.encode('ascii')
    base64_message_bytes = base64.b64decode(base64_secret_bytes)
    secret_content = base64_message_bytes.decode('ascii')
    return secret_content
```

Handlerでの呼び出し部分

```
    secret_contents = read_secret_value(secret_client, secret_id)
    dbpwd = format(secret_contents)
    load_data(signer, namespace, input_bucket, object_name, ordsbaseurl, schema, dbuser, dbpwd)
    move_object(signer, namespace, input_bucket, processed_bucket, object_name)
```

ファンクションに構成を追加
ORDS URL、ADBスキーマ、Object Storageバケット名、シークレットOCID

```
fn config function <APP NAME> <function NAME> ords-base-url https://G8HITPQLBTFUF9F-ORCLATP.adb.ap-tokyo-1.oraclecloudapps.com/ords/
fn config function <APP NAME> <function NAME> db-schema "admin"
fn config function <APP NAME> <function NAME> db-user "admin"
fn config function <APP NAME> <function NAME> input-bucket "input-bucket"
fn config function <APP NAME> <function NAME> processed-bucket "processed-bucket"
fn config function <APP NAME> <function NAME> password_id "ocid1.vaultsecret.oc1.iad.amaaaaaa“
```

デプロイするアプリケーションを指定し、ファンクションをデプロイ

```
fn deploy --app <AP名>
```

## イベント・ルールの作成
バケットにファイルを配置したタイミングでFunctionをトリガーするイベントを設定
OCI コンソール > アプリケーション統合 > イベント・サービス に移動
![図6.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108635/6f57345f-059c-58ec-7821-e443804e85a3.jpeg)

- [ルールの作成］をクリック
    - 表示名・説明を入力
    - ルール条件に以下3条件を入力
        - イベント・タイプ , サービス名 = Object Storage, イベント・タイプ = Object - Create
        - 属性, 属性名 = compartmentName, 属性値 = <コンパートメント名>
        - 属性, 属性名 = bucketName, 属性値 = <input bucket>
    - アクション
        - アクション・タイプ = ファンクション
        - ファンクション・コンパートメント = <コンパートメント名>
        - 作成した、ファンクション・アプリケーション、ファンクションを指定
![図7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108635/2bf54254-ee0f-4ca3-fd66-7bdee31aa5eb.png)


# 動作確認
input-bucket に file1.csvをアップロード

```functions.log
  INFO - Event ID 63062746-0a2d-2eff-d928-927e982b2d5e received
  INFO - Object name: file1.csv
  INFO - Bucket name: input-bucket
  INFO - Namespace: XXXXXXX7
  INFO - Object file1.csv is read
  INFO - inserting:
  INFO - {"region": "AMER", "col1": "1", "col2": "2", "col3": "3"}
  INFO - Successfully inserted document ID 2C7207FFA0F64FE8A68F737FA6760939
  INFO - inserting:
  INFO - {"region": "APAC", "col1": "4", "col2": "5", "col3": "6"}
  INFO - Successfully inserted document ID 562F77FA21014EA08BFFF1FBB0B3D77E
  INFO - inserting:
  INFO - {"region": "EMEA", "col1": "7", "col2": "8", "col3": "9"}
  INFO - Successfully inserted document ID 7AE7BD6D992A48A3AC70234AABB6C550
  INFO - inserting:
  INFO - {"region": "AMER", "col1": "10", "col2": "11", "col3": "12"}
  INFO - Successfully inserted document ID 1BF89A74B20A46A18A3E7F52565B2762
  INFO - All documents are successfully loaded into the database
  INFO - Object file1.csv moved to Bucket processed-bucket
  2020-05-05 02:22:16,055 - fdk.event_handler - INFO - request execution completed
```

#### ロードの確認
SQLでregionsnumbersコレクションにアクセス

```
select 
 id,
 r.json_document.region,
	r.json_document.col1,
	r.json_document.col2,
	r.json_document.col3,
from regionsnumbers r

"ID"    				"REGION" "COL1"  "COL2"  "COL3"  
"78709441BFA2447883D185EEEFDCA0D3"  	"APAC"  	"4" 	"5" 	"6" 
"A0A8FC73B54C4665AD321693E2E35F09"  	"APAC"  	"16"    	"17"    "18"
"AFEF7F4544C24E4482687FDBAD5ED197"  	"AMER"  	"22"    	"23"    "24"
"6497837A71614CE6B1F788D2369B49C7"  	"AMER"  	"13"    	"14"    "15"
```

# おわりに
イベント・サービスとFunctionsを使って、Object StorageにUploadされたファイルのAutonomous Databaseへのロード自動化ができた。

# 参考情報

- [Using the OCI Instance Principals and Vault with Python to retrieve a Secret]( https://www.ateam-oracle.com/using-the-oci-instance-principals-and-vault-with-python-to-retrieve-a-secret)
- [Automatically load data from Object Storage into Autonomous Data Warehouse](https://github.com/oracle/oracle-functions-samples/tree/master/oci-load-file-into-adw-python)
- [Cloud Shellを使ってOracle Functionsサンプルを動かしてみた。](https://qiita.com/kenwatan/items/d8fd2426e93a1cfb65f6)
- [[AutonomousDB] Autonomous DatabaseにJSONドキュメントを格納してみた。（ORDS+SODA)](https://qiita.com/kenwatan/items/db9134e1c9bf49315a79)
- [Oracle Functionsことはじめ](https://oracle-japan.github.io/paasdocs/documents/faas/oraclefunctions/handson/getting-started/)
- [Autonomous Database ハンズオンラボ（ADB HOL）](https://community.oracle.com/docs/DOC-1034727)