AWS Lambda (Node.js-v4.3.2)からOracleに接続する(ORA-21561への対応)

__<font color="red">ORA-21561の部分に関して2016/11/7に追記しています。</font>__

AWS Lambda(in VPC)からOracleに接続する時に数時間ハマったので記録として残しておきます

気付いてしまえばたいしたことないんですが、途中にある「ORA-21561: OID generation failed」のへの対応で全作業時間の9割程度なやんでいました

# AWS Lambda (Node.js-v4.3.2)の実行環境に近いEC2環境の構築
[AWS Lambda (Node.js-v4.3.2)の実行環境に近いEC2環境の構築](http://qiita.com/ikeisuke/items/b32e0ae51a188075ed17)を参考にしてください

# Oracle InstantClientのインストール

[Instant Client Downloads for Linux x86-64](http://www.oracle.com/technetwork/topics/linuxx86-64soft-092277.html)(要ログイン)からrpmをダウンロードします

必要なものは以下の２つです
oracle-instantclient12.1-basic-12.1.0.2.0-1.x86_64.rpm
oracle-instantclient12.1-devel-12.1.0.2.0-1.x86_64.rpm 

ダウンロードしたらインストールします

```bash:terminal
$ sudo rpm -i oracle-instantclient12.1-basic-12.1.0.2.0-1.x86_64.rpm oracle-instantclient12.1-devel-12.1.0.2.0-1.x86_64.rpm
```

# nodejsからOracleへの接続

## 接続するためのコードを書く

### プロジェクトの作成とoracledbのインストール

```bash:terminal
$ mkdir oracletest
$ cd oracletest
$ npm init
$ npm install oracledb --save
```

### テスト用コードの作成

process.env.NODE_ORACLEDB_* に関しては適宜書き換え・環境変数として渡すなどしてください
※接続用の環境変数は公式の[テストコード](https://github.com/oracle/node-oracledb/blob/master/test/dbconfig.js)を参考にしてます


```javascript:index.js
'use strict';

process.env.NODE_ORACLEDB_USER='user'
process.env.NODE_ORACLEDB_PASSWORD='password'
process.env.NODE_ORACLEDB_CONNECTIONSTRING='hostname:port/servicename'

const oracledb = require('oracledb');

exports.handler = function(event, context, callback) {
  console.log('start');
  oracledb.getConnection({
    user : process.env.NODE_ORACLEDB_USER,
    password : process.env.NODE_ORACLEDB_PASSWORD,
    connectString : process.env.NODE_ORACLEDB_CONNECTIONSTRING,
  },
  function(err, connection) {
    if (err) { callback(err); return; }
    console.log('connected');
    connection.execute("SELECT * FROM [table] WHERE ROWNUM < 10", [], function(err, result) {
      if (err) { callback(err); return; }
      callback(null, result.rows);
    });
  });
}
```

```javascript:test.js
'use strict';

const t = require('./index');
t.handler({}, {}, function(error, result){
  console.log(error);
  console.log(result);
});
```

### 実行
```bash:terminal
$ node test.js
start
[Error: ORA-21561: OID generation failed
]
undefined
```

## ORA-21561: OID generation failedへの対応

__2016/11/07追記 ここから__
その後動作を確認したりいろいろしていたところ、「DHCP Options Sets」の設定の問題だと気付きました。
VPC内で独自でDNSサーバーを構築している関係で、デフォルトで設定されているはずの「domain-name = ap-northeast-1.compute.internal」を解除していたようです。
domain-nameを追加した設定を作成し、DHCP Options setに設定したところ、LOCALDOMAINの設定なしで動作することを確認できました。
__2016/11/07追記 ここまで__

[ぐぐる](https://www.google.co.jp/search?q=ORA-21561%3A%20OID%20generation%20failed&rct=j)とわかりますが、/etc/hostsを書き換えるのが標準的な対応のようです

今回は、AWS Lambdaのため設定ファイルに触れないのでどうしたらいいのか悩みました
問題は自ホストのホスト名が引けないことが原因なので以下の対応を行いました

* VPCの`DNS Hostnames`を有効してVPCローカルなドメインを自ホストに紐付ける
* 環境変数(LOCALDOMAIN)を利用して検索対象のドメインを設定、上記のドメインを引けるようにする

```
LOCALDOMAIN='ap-northeast-1.compute.internal'
```
※ ap-northeast-1の部分はVPCのリージョンによって切り分ける必要があります
※ [AWS Lambdaの環境変数](http://qiita.com/ikeisuke/items/b32e0ae51a188075ed17#aws-lambda%E3%81%AE%E7%92%B0%E5%A2%83%E5%A4%89%E6%95%B0)を使えばリージョンごとの切り替えは自動にできそうです

コードはこのようになりました（3行目が追加されただけ）

```javascript:index.js
'use strict';

process.env.LOCALDOMAIN='ap-northeast-1.compute.internal';
process.env.NODE_ORACLEDB_USER='user'
process.env.NODE_ORACLEDB_PASSWORD='password'
process.env.NODE_ORACLEDB_CONNECTIONSTRING='hostname:port/servicename'

const oracledb = require('oracledb');

exports.handler = function(event, context, callback) {
  console.log('start');
  oracledb.getConnection({
    user : process.env.NODE_ORACLEDB_USER,
    password : process.env.NODE_ORACLEDB_PASSWORD,
    connectString : process.env.NODE_ORACLEDB_CONNECTIONSTRING,
  },
  function(err, connection) {
    if (err) { callback(err); return; }
    console.log('connected');
    connection.execute("SELECT * FROM [table] WHERE ROWNUM < 10", [], function(err, result) {
      if (err) { callback(err); return; }
      callback(null, result.rows);
    });
  });
}
```


### 再実行
```bash:terminal
$ node test.js
start
connected
null
[ [ 1, 2, 3], [ 3, 4, 5] ]
```

### Lambdaへのアップロード

まずはアップロード用にzipアーカイブを作成します

```bash:terminal
zip -r oracletest.zip package.json index.js node_modules/
```


### 実行結果
アップロードして実行すると以下のようなエラーが発生しました

```text:LogOutput
START RequestId: c4365deb-89fc-11e6-a186-f96f2fe34754 Version: $LATEST
2016-10-04T06:35:52.219Z	c4365deb-89fc-11e6-a186-f96f2fe34754	start
2016-10-04T06:35:52.357Z	c4365deb-89fc-11e6-a186-f96f2fe34754	Error: libclntsh.so.12.1: cannot open shared object file: No such file or directory
    at Error (native)
    at Object.Module._extensions..node (module.js:434:18)
    at Module.load (module.js:343:32)
    at Function.Module._load (module.js:300:12)
    at Module.require (module.js:353:17)
    at require (internal/module.js:12:17)
    at Object.<anonymous> (/var/task/node_modules/oracledb/lib/oracledb.js:35:19)
    at Module._compile (module.js:409:26)
    at Object.Module._extensions..js (module.js:416:10)
    at Module.load (module.js:343:32)
END RequestId: c4365deb-89fc-11e6-a186-f96f2fe34754
REPORT RequestId: c4365deb-89fc-11e6-a186-f96f2fe34754	Duration: 187.52 ms	Billed Duration: 200 ms 	Memory Size: 128 MB	Max Memory Used: 15 MB	
Process exited before completing request
```

## OracleのSharedObjectのアップロード

アーカイブにsoファイルを含んでいないためだと思われるので、必要そうなものをピックアップして一緒にzipします

```bash:terminal
mkdir lib
cp /usr/lib/oracle/12.1/client64/lib/libclntsh.so.12.1 ./lib/
cp /usr/lib/oracle/12.1/client64/lib/libclntshcore.so.12.1 ./lib/
cp /usr/lib/oracle/12.1/client64/lib/libipc1.so ./lib/
cp /usr/lib/oracle/12.1/client64/lib/libmql1.so ./lib/
cp /usr/lib/oracle/12.1/client64/lib/libnnz12.so ./lib/
cp /usr/lib/oracle/12.1/client64/lib/libocci.so ./lib/
cp /usr/lib/oracle/12.1/client64/lib/libociei.so ./lib/
cp /usr/lib/oracle/12.1/client64/lib/libons.so ./lib/
cp /lib64/libaio.so.1 ./lib/
```
[AWS Lambdaの環境変数](http://qiita.com/ikeisuke/items/b32e0ae51a188075ed17#aws-lambda%E3%81%AE%E7%92%B0%E5%A2%83%E5%A4%89%E6%95%B0)を見るとわかりますが、LD_LIBRARY_PATHにプロジェクトのlibディレクトリが含まれているようなのでsoファイルをlibディレクトリ以下にコピーしています

### Lambdaへの再アップロード

再アップロード用にzipファイルを作成します。

```bash:terminal
zip -r oracletest.zip package.json index.js node_modules/ lib/
```

最初のアップロードでは直接AWS Lambdaにアップロードできましたが、今回はsoファイルのを含むため、直接アップロード可能な50MBを超えています
そのため、今回はzipファイルをs3にアップロードし、AWS Lambda側ではそのリンクを指定してアップロードします

### 再実行結果
以上の対応で問題なくAWS Lambda(nodejs)からOracleへの接続ができるようになりました。
※ 再実行で出ていた取得したレコードはログではなく、AWS Lambdaの実行結果として返却されています

```test:LogOutput
START RequestId: d6e7bac8-8a01-11e6-8fbc-1b17ce22866f Version: $LATEST
2016-10-04T07:12:01.578Z	d6e7bac8-8a01-11e6-8fbc-1b17ce22866f	start
2016-10-04T07:12:01.938Z	d6e7bac8-8a01-11e6-8fbc-1b17ce22866f	connected
END RequestId: d6e7bac8-8a01-11e6-8fbc-1b17ce22866f
REPORT RequestId: d6e7bac8-8a01-11e6-8fbc-1b17ce22866f	Duration: 389.55 ms	Billed Duration: 400 ms 
```