[OCI]指定したコンパートメント内の全てのComputeインスタンスをAutonomous Databaseから一括で起動/停止する

# はじめに

Autonomous Databaseの検証作業後に、検証環境のComputeインスタンスを停止し忘れてしまうことがよくあります。
そこで、検証用コンパートメントにあるComputeインスタンスをAutonomous Databaseからまとめて停止できたら手間が省けると思い、それを実現するPL/SQLプロシージャを作成してみました。

※こちらのプロシージャはあくまでサンプルです。
　実環境でお使いになる場合は、適切なエラーハンドリング等を実装した上で、充分な検証を実施してください。
# 1. PL/SQLプロシージャの作成
今回は、コンパートメント名、リージョン識別子、実行したいアクションをパラメータとして渡すと、指定したリージョンにある指定したコンパートメント内の全てのComputeインスタンスを起動/停止するプロシージャmanage_compute_instancesを作成します。

こちらのプロシージャでは、Autonomous Databaseのリソース・プリンシパルを使用していますので、こちらのマニュアルを参照して、事前にリソース・プリンシパルを有効化しておきます。

https://docs.oracle.com/cd//E83857_01/paas/autonomous-database/adbsa/resource-principal.html#GUID-E283804C-F266-4DFB-A9CF-B098A21E496A

※DBMS_CLOUD.CREATE_CREDENTIALプロシージャで作成したユーザ認証ベースのクレデンシャルも、もちろん使用可能です。

manage_compute_instancesのソースがこちらです。
各処理の詳細は、ソース内のコメントをご確認ください。

```sql:manage_compute_instancesプロシージャ
CREATE OR REPLACE PROCEDURE manage_compute_instances( target_compartment_name IN VARCHAR2, target_region IN VARCHAR2, action IN VARCHAR2)
    IS
        target_lifecyclestate  VARCHAR2(100);
        region                 VARCHAR2(100);
        root_ocid              VARCHAR2(100);
        compartment_ocid       VARCHAR2(100);
        request_uri            VARCHAR2(200);
        resp                   DBMS_CLOUD_TYPES.resp;
        instance_list          JSON_ARRAY_T  := JSON_ARRAY_T();
        instance_object        JSON_OBJECT_T := JSON_OBJECT_T();
        instance_ocid          VARCHAR2(200); 

    BEGIN
        IF (UPPER(action) = 'START')
          THEN
            target_lifecyclestate := 'Stopped';
          ELSIF (UPPER(action) = 'STOP' OR UPPER(action) = 'SOFTSTOP')
            THEN target_lifecyclestate := 'Running';
          ELSE RETURN;
        END IF;
    -- ルートコンパートメントのOCIDとADBが稼働しているリージョンを取得
        SELECT json_value(cloud_identity, '$.REGION'), LOWER(json_value(cloud_identity, '$.TENANT_OCID')) INTO region, root_ocid FROM v$pdbs;
    -- 対象コンパートメントの情報を取得するREST APIのURIを生成
        request_uri := 'https://identity.'||region||'.oraclecloud.com/20160918/compartments/?compartmentId='||root_ocid||CHR(38)||'name='||target_compartment_name;
    -- REST APIをコールして対象コンパートメントの情報を取得
        resp := DBMS_CLOUD.send_request(
                  credential_name => 'OCI$RESOURCE_PRiNCIPAL',
                  uri             => request_uri,
                  method          => DBMS_CLOUD.METHOD_GET
                );
    -- 取得したコンパートメントの情報からコンパートメントのOCIDを取得
        compartment_ocid := JSON_VALUE(DBMS_CLOUD.get_response_text(resp), '$.id');
    -- デバッグ用出力
        DBMS_OUTPUT.put_line ('Compartment Name  : '||JSON_VALUE(DBMS_CLOUD.get_response_text(resp), '$.name'));
        DBMS_OUTPUT.put_line ('Compartment OCID : ' ||JSON_VALUE(DBMS_CLOUD.get_response_text(resp), '$.id')||CHR(10));
    -- 対象コンパートメント内にある対象となる状態にあるComputeインスタンスのリストを取得するREST APIのURIを生成
        request_uri := 'https://iaas.'||target_region||'.oraclecloud.com/20160918/instances/?compartmentId='||compartment_ocid||CHR(38)||'lifecycleState='||target_lifecyclestate;
    -- REST APIをコールして対象コンパートメント内に対象となる状態にあるComputeインスタンスのリストを取得
        resp := DBMS_CLOUD.send_request(
                  credential_name => 'OCI$RESOURCE_PRiNCIPAL',
                  uri             => request_uri,
                  method          => DBMS_CLOUD.METHOD_GET
                );
    -- 取得したComputeインスタンスのリストを配列に格納
        instance_list := JSON_ARRAY_T(DBMS_CLOUD.get_response_text(resp));
    -- デバッグ用出力
        DBMS_OUTPUT.put_line ('Number of Instances : ' || instance_list.get_size||CHR(10));
    -- 取得したComputeインスタンスのリスト内にある各Computeインスタンスに対する操作
        FOR indx IN 0 .. instance_list.get_size - 1
            LOOP
    -- 取得したComputeインスタンスのリスト（配列）からComputeインスタンスの情報を取得
                instance_object := TREAT (instance_list.get (indx) AS json_object_t);
    -- 取得したComputeインスタンスの情報からComputeインスタンスのOCIDを取得
                instance_ocid := instance_object.get_string('id');
    -- デバッグ用出力
                DBMS_OUTPUT.put_line ('Instance Display Name  : '||instance_object.get_string('displayName'));
                DBMS_OUTPUT.put_line ('Instance OCID : '||instance_ocid);
    -- Computeインスタンスに対して指定したアクションを実行するREST APIのURIを生成
                request_uri := 'https://iaas.'||target_region||'.oraclecloud.com/20160918/instances/'||instance_ocid||'?action='||UPPER(action);
    -- REST APIをコールしてComputeインスタンスに対するアクションを実行
                resp := DBMS_CLOUD.send_request(
                          credential_name => 'OCI$RESOURCE_PRiNCIPAL',
                          uri             => request_uri,
                          method          => DBMS_CLOUD.METHOD_POST,
                          body            => UTL_RAW.CAST_TO_RAW('{"actionType" : "softreset"}')
                        );
            END LOOP;
    END;
/
```
上記の内容をSQL*PlusやDatabase Actionから実行して、プロシージャを作成します。
```
SQL> CREATE OR REPLACE PROCEDURE manage_compute_instances( target_compartment_name IN VARCHAR2, target_region IN VARCHAR2, action IN VARCHAR2)
  2  	 IS
  3  	     target_lifecyclestate  VARCHAR2(100);
  4  	     region		            VARCHAR2(100);
  5  	     root_ocid		        VARCHAR2(100);
  6  	     compartment_ocid	    VARCHAR2(100);
  7  	     request_uri	        VARCHAR2(200);
  8  	     resp		            DBMS_CLOUD_TYPES.resp;
  9  	     instance_list	        JSON_ARRAY_T  := JSON_ARRAY_T();
 10  	     instance_object	    JSON_OBJECT_T := JSON_OBJECT_T();
 11  	     instance_ocid	        VARCHAR2(200);
 12  
 13  	 BEGIN
 14  	     IF (UPPER(action) = 'START')
 15  	       THEN
 16  		 target_lifecyclestate := 'Stopped';
 17  	       ELSIF (UPPER(action) = 'STOP' OR UPPER(action) = 'SOFTSTOP')
 18  		 THEN target_lifecyclestate := 'Running';
 19  	       ELSE RETURN;
 20  	     END IF;
 21  
 22  	     SELECT json_value(cloud_identity, '$.REGION'), LOWER(json_value(cloud_identity, '$.TENANT_OCID')) INTO region, root_ocid FROM v$pdbs;
 23  
 24  	     request_uri := 'https://identity.'||region||'.oraclecloud.com/20160918/compartments/?compartmentId='||root_ocid||CHR(38)||'name='||target_compartment_name;
 25  	     resp := DBMS_CLOUD.send_request(
 26  		           credential_name => 'OCI$RESOURCE_PRiNCIPAL',
 27  		           uri             => request_uri,
 28  		           method          => DBMS_CLOUD.METHOD_GET
 29  		         );
 30  
 31  	     compartment_ocid := JSON_VALUE(DBMS_CLOUD.get_response_text(resp), '$.id');
 32  	     DBMS_OUTPUT.put_line ('Compartment Name  : '||JSON_VALUE(DBMS_CLOUD.get_response_text(resp), '$.name'));
 33  	     DBMS_OUTPUT.put_line ('Compartment OCID : ' ||JSON_VALUE(DBMS_CLOUD.get_response_text(resp), '$.id')||CHR(10));
 34  
 35  	     request_uri := 'https://iaas.'||target_region||'.oraclecloud.com/20160918/instances/?compartmentId='||compartment_ocid||CHR(38)||'lifecycleState='||target_lifecyclestate;
 36  	     resp := DBMS_CLOUD.send_request(
 37  		           credential_name => 'OCI$RESOURCE_PRiNCIPAL',
 38  		           uri             => request_uri,
 39  		           method          => DBMS_CLOUD.METHOD_GET
 40  		         );
 41  
 42  	     instance_list := JSON_ARRAY_T(DBMS_CLOUD.get_response_text(resp));
 43  	     DBMS_OUTPUT.put_line ('Number of Instances : ' || instance_list.get_size||CHR(10));
 44  
 45  	     FOR indx IN 0 .. instance_list.get_size - 1
 46  		 LOOP
 47  		     instance_object := TREAT (instance_list.get (indx) AS json_object_t);
 48  		     instance_ocid := instance_object.get_string('id');
 49  		     DBMS_OUTPUT.put_line ('Instance Display Name  : '||instance_object.get_string('displayName'));
 50  		     DBMS_OUTPUT.put_line ('Instance OCID : '||instance_ocid);
 51  
 52  		     request_uri := 'https://iaas.'||target_region||'.oraclecloud.com/20160918/instances/'||instance_ocid||'?action='||UPPER(action);
 53  		     resp := DBMS_CLOUD.send_request(
 54  			           credential_name => 'OCI$RESOURCE_PRiNCIPAL',
 55  			           uri             => request_uri,
 56  			           method          => DBMS_CLOUD.METHOD_POST,
 57  			           body            => UTL_RAW.CAST_TO_RAW('{"actionType" : "softreset"}')
 58  			         );
 59  		 END LOOP;
 60  	 END;
 61  /

プロシージャが作成されました。

SQL> 
```
PL/SQLプロシージャmanage_compute_instancesが作成できました。

manage_compute_instancesプロシージャを実行するには、こちらのような形式で、コンパートメント名、リージョン識別子、実行したいアクションをパラメータとして渡す必要があります。
```sql
exec manage_compute_instances('コンパートメント名','リージョン識別子','アクション')
```
コンパートメント名：コンパートメントの名前（大文字小文字も一致する必要あり）
リージョン識別子：ap-tokyo-1、ap-osaka-1など
アクション：start、stop、softstopのいずれか（大文字、小文字どちらでもOK）

# 2. 作成したPL/SQLプロシージャの動作確認

ここでは東京リージョン（ap-tokyo-1）のTestコンパートメント内にあるComputeインスタンスをアクションの対象として動作を確認してみます。

確認のために、OCIコンソールで東京リージョンのTestコンパートメント内にあるComputeインスタンスのリストを表示しておきます。

Testコンパートメント内には、Compute1、Compute2、Compute3の3つのComputeインスタンスがあり、全て状態が「停止中」になっています。
![スクリーンショット 2022-12-11 16.53.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/784824/903f5852-cc72-2432-0b34-57a5da92f819.png)
では、コンパートメント名に「Test」、リージョンに「ap-tokyo-1」、アクションに「start」を指定してmanage_compute_instancesプロシージャを実行してみます。
```sql
exec manage_compute_instances('Test','ap-tokyo-1','start')
```
上記の内容をSQL*PlusやDatabase Actionから実行します。
```
SQL> set serveroutput on
SQL> exec manage_compute_instances('Test','ap-tokyo-1','start')
Compartment Name  : Test
Compartment OCID :
ocid1.compartment.oc1..aaaxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxwlq


Number of Instances : 3


Instance Display Name  : Compute1
Instance OCID :
ocid1.instance.oc1.ap-tokyo-1.anxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx2bq
Instance Display Name  : Compute2
Instance OCID :
ocid1.instance.oc1.ap-tokyo-1.anxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxjjq
Instance Display Name  : Compute3
Instance OCID :
ocid1.instance.oc1.ap-tokyo-1.anxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxdwq

PL/SQLプロシージャが正常に完了しました。

SQL>
```
プロシージャの実行が正常に完了したので、OCIコンソールを確認してみます。
Testコンパートメント内の全てComputeインスタンスの状態が「起動中」になりました。
![スクリーンショット 2022-12-11 17.03.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/784824/e280ec36-8b01-4e35-063f-e2dc5036cd23.png)
少し待つと、全てのTestコンパートメント内の全てComputeインスタンスの状態が「実行中」になりました。
![スクリーンショット 2022-12-11 17.04.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/784824/5cb3147d-f703-1154-b57d-ec9abba5233c.png)
次に、コンパートメント名に「Test」、リージョンに「ap-tokyo-1」、アクションに「softstop」を指定してmanage_compute_instancesプロシージャを実行してみます。
```
SQL> exec manage_compute_instances('Test','ap-tokyo-1','softstop')
Compartment Name  : Test
Compartment OCID :
ocid1.compartment.oc1..aaaxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxwlq


Number of Instances : 3


Instance Display Name  : Compute1
Instance OCID :
ocid1.instance.oc1.ap-tokyo-1.anxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx2bq
Instance Display Name  : Compute2
Instance OCID :
ocid1.instance.oc1.ap-tokyo-1.anxhxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxjjq
Instance Display Name  : Compute3
Instance OCID :
ocid1.instance.oc1.ap-tokyo-1.anxhxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxdwq

PL/SQLプロシージャが正常に完了しました。

SQL>
```
プロシージャの実行が正常に完了したので、OCIコンソールを確認してみます。
Testコンパートメント内の全てComputeインスタンスの状態が「停止中」になりました。
![スクリーンショット 2022-12-11 17.06.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/784824/3d2b49cf-b224-4023-a1a1-923c1973d841.png)
少し待つと、全てのTestコンパートメント内の全てComputeインスタンスの状態が「実行中」になりました。
![スクリーンショット 2022-12-11 17.10.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/784824/d98b9838-80a8-005a-477d-3e93883a2765.png)
起動、停止ともに想定通りの動作となっていることが確認できました。

コメント、デバッグ用出力を削除したmanage_compute_instancesプロシージャの完成版は以下のようになります。

```sql:manage_compute_instancesプロシージャ 完成版
CREATE OR REPLACE PROCEDURE manage_compute_instances( target_compartment_name IN VARCHAR2, target_region IN VARCHAR2, action IN VARCHAR2)
    IS
        target_lifecyclestate  VARCHAR2(100);
        region                 VARCHAR2(100);
        root_ocid              VARCHAR2(100);
        compartment_ocid       VARCHAR2(100);
        request_uri            VARCHAR2(200);
        resp                   DBMS_CLOUD_TYPES.resp;
        instance_list          JSON_ARRAY_T  := JSON_ARRAY_T();
        instance_object        JSON_OBJECT_T := JSON_OBJECT_T();
        instance_ocid          VARCHAR2(200); 

    BEGIN
        IF (UPPER(action) = 'START')
          THEN
            target_lifecyclestate := 'Stopped';
          ELSIF (UPPER(action) = 'STOP' OR UPPER(action) = 'SOFTSTOP')
            THEN target_lifecyclestate := 'Running';
          ELSE RETURN;
        END IF;

        SELECT json_value(cloud_identity, '$.REGION'), LOWER(json_value(cloud_identity, '$.TENANT_OCID')) INTO region, root_ocid FROM v$pdbs; 

        request_uri := 'https://identity.'||region||'.oraclecloud.com/20160918/compartments/?compartmentId='||root_ocid||CHR(38)||'name='||target_compartment_name;
        resp := DBMS_CLOUD.send_request(                  credential_name => 'OCI$RESOURCE_PRiNCIPAL',
                  uri             => request_uri,
                  method          => DBMS_CLOUD.METHOD_GET
                );
        compartment_ocid := JSON_VALUE(DBMS_CLOUD.get_response_text(resp), '$.id');    

        request_uri := 'https://iaas.'||target_region||'.oraclecloud.com/20160918/instances/?compartmentId='||compartment_ocid||CHR(38)||'lifecycleState='||target_lifecyclestate;
        resp := DBMS_CLOUD.send_request(                  credential_name => 'OCI$RESOURCE_PRiNCIPAL',
                  uri             => request_uri,
                  method          => DBMS_CLOUD.METHOD_GET
                );
        instance_list := JSON_ARRAY_T(DBMS_CLOUD.get_response_text(resp));    -- デバッグ用出力

        FOR indx IN 0 .. instance_list.get_size - 1
            LOOP
                instance_object := TREAT (instance_list.get (indx) AS json_object_t);  
                instance_ocid := instance_object.get_string('id');

                request_uri := 'https://iaas.'||target_region||'.oraclecloud.com/20160918/instances/'||instance_ocid||'?action='||UPPER(action);
                resp := DBMS_CLOUD.send_request(                          credential_name => 'OCI$RESOURCE_PRiNCIPAL',
                          uri             => request_uri,
                          method          => DBMS_CLOUD.METHOD_POST,
                          body            => UTL_RAW.CAST_TO_RAW('{"actionType" : "softreset"}')
                        );
            END LOOP;
    END;
/
```


# まとめ

PL/SQLプロシージャを使用して、指定したコンパートメント内の全てのComputeインスタンスをAutonomous Databaseから起動/停止することができました。

こちらのプロシージャはAlways FreeのAutonomous Databaseでも作成可能ですので、プロシージャをAlways FreeのAutonomous Database内に作成し、DBMS_SCHEDULERで一定間隔で自動実行することで、検証環境のComputeインスタンスを毎朝自動的に起動し、毎晩自動的に停止するといった運用も追加コストなしで可能になるかもしれませんね。

めでたし、めでたし。

# 参考資料
・[DBMS_CLOUD.SEND_REQUEST](https://docs.oracle.com/cd//E83857_01/paas/autonomous-database/adbsa/dbms-cloud-subprograms.html#GUID-B063870D-6C1F-4F33-B354-885B73C81D37)
・[Oracle Cloud Infrastructure Documentation / API Reference : ListCompartments](https://docs.oracle.com/en-us/iaas/api/#/en/identity/20160918/Compartment/ListCompartments)
・[Oracle Cloud Infrastructure Documentation / API Reference : ListInstances](https://docs.oracle.com/en-us/iaas/api/#/en/iaas/20160918/Instance/ListInstances)
・[Oracle Cloud Infrastructure Documentation / API Reference : InstanceAction](https://docs.oracle.com/en-us/iaas/api/#/en/iaas/20160918/Instance/InstanceAction)