Automation PilotでSAP HANA Cloudの稼働状態を確認し、再起動するコマンドを作成

## やりたいこと
トライアル環境のSAP HANA Cloudのインスタンスは毎日自動的に停止するので、使いたいときにすぐに使えるよう、停止状態を確認したら再起動したいです。

## これまでにやったことと、課題
以前の記事で、毎日決まった時刻にインスタンスの起動をスケジュールすることはできました。

https://qiita.com/tami/items/fb369b2a34b878b29064

https://qiita.com/tami/items/840383ec6c9cf8676ede

HANA Cloudの稼働状況を確認すると、日本時間の15時頃に停止していることがわかりますが、停止時刻には若干の変動があるようです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/0e4b4ab6-23e9-598f-67bd-abc5727f2db8.png)

停止から起動までの時間を最小にできないだろうか、と思ったことが今回の検証のきっかけです。


※単純に起動を5分おきにスケジュールするでもいいのですが、Automation Pilotで色々やってみたいために検証しました

## 参考にしたもの
Automation Pilotのサンプルリポジトリにある**Check HANA Cloud Availability**というコマンドを参考に作成しました。

https://github.com/SAP-samples/automation-pilot-examples/tree/main/check-hana-cloud-availability

以下で参考にしたコマンドについて解説します。

### インプット、アウトプット
インプットはHANA Cloudに関する情報、BTPのログイン情報など多数、アウトプットは3つです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/ea5b50f3-d478-1b7c-b689-d73030d0652c.png)

### コマンド
このコマンドは4つのステップから構成されています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/d0a8f7cd-e5ae-a22a-e5f0-44aea97ff3e7.png)

1. HANA CloudのAPIアクセス用のトークンを取得
1. APIを使ってHANA Cloudの状態を確認
1. （HANA Cloudが停止している場合）Alert Notificationに通知
1. （HANA Cloudが停止している場合）HANA Cloudを起動

次に、今回作成しようとしているコマンドに関連するポイントについて説明します。

#### APIを使ってHANA Cloudの状態を確認
SAP HANA CloudのMetrics ServiceのAPIを使ってHANA Cloudの状態を取得しています。
https://api.sap.com/api/MetricsAPI/path/get_metrics_v1_serviceInstances__serviceInstanceID__values

`names=HDBAccessible`というパラメータをつけて呼び出すと、直近1時間の30秒ごとの状態を返してくれます。valueに"1"が設定されている場合はアクセス可能な状態です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/581cdfa0-c49d-40c3-a0bd-08b363b0237d.png)

#### HANA Cloudを起動
`dblm-sapcp:StartHanaCloudInstance:1`というコマンドを使っています。これはCloud Foundryに作成されたインスタンスを起動するコマンドです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/38c5f5e7-2db2-4688-55d3-7caa7fb7f1db.png)

今回は"Other Environments"で作成したHANA Cloudインスタンスを起動するので、`sm-sapcp:UpdateServiceInstance:1`というコマンドを使用します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/2668d3bc-b149-7482-85eb-6a2a6bfe4a1e.png)

## 作成したもの
### インプット、アウトプット
コマンドで必要なパラメータはHANA Cloudのサービスキーと、Service Managerのサービスキーにすべて入っているので、この2つをインプットとします。アウトプットは参考にしたコマンドと同様です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/aac724cc-b362-0c26-753c-9de2033a1c96.png)

HANA Cloudのサービスキーは、Service Bindingで作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/2eba65e3-7f69-536f-d70b-1d330987583c.png)

### コマンド
ステップは以下の2つです。**pingHanaCloud**のステップで使っている`http-sapcp:HttpRequest:1`というコマンドがOAuth 2.0 Client Credentials Flowに対応しているので、事前にトークンを取得するステップは不要になりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/b22425b4-2db8-ab33-d0c5-e436c28d6bcf.png)

#### HANA Cloudの状態を確認
`http-sapcp:HttpRequest:1`コマンドのパラメータを以下のように設定しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/cd2a1df1-4ece-aca9-ee81-b88065e40100.png)

URLはサービスキーのhostの値をもとに、式を使って設定しています。
```
https://api.gateway.orchestration.$(.execution.input.hanaCloudServiceKey.host | split(".") | .[2:] | join("."))/metrics/v1/serviceInstances/$(.execution.input.hanaCloudServiceKey.host | split(".")[0])/values?names=HDBAccessible
```

:::note
式の作成では全体的にChatGPTを活用しました。利用可能なjqの式には制限があるので、以下のヘルプを参照します。
https://help.sap.com/docs/automation-pilot/automation-pilot/dynamic-expression

サンプルのコマンドではインプットパラメータが多数ありましたが、jqを駆使することでサービスキーのみにできたのがよかったです。
:::

**hostの値**
`37b4cc7b-2eb5-4e06-912d-511d1a50135e`.hana.`trial-us10.hanacloud.ondemand.com`

**設定されたURL**（網掛けがhostから設定された部分）
https
://api.gateway.orchestration.`trial-us10.hanacloud.ondemand.com`/metrics/v1/serviceInstances/`37b4cc7b-2eb5-4e06-912d-511d1a50135e`/values?names=HDBAccessible

レスポンスボディにはHANA Cloudの状態が時系列（古いものが上）で並んでいるので、直前の状態を確認するためにAdvancedタブのResponse Body Transformerで逆順に並べ替えています。これは、参考にしたコマンドの設定と同じです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/ecafd5f3-db1e-cfca-e0a0-4d083a08f92e.png)

#### HANA Cloudを起動
HANA Cloudが停止している場合、`sm-sapcp:UpdateServiceInstance:1`というコマンドを使い、HANA Cloudを起動します。

「停止している場合」という条件は、コマンドのConditionで以下のように設定します。直近の3つ（90秒間）のHANA Cloudの状態で1つも稼働状態がない場合、ということです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/b07c1abc-2743-55c3-15d4-1bffab22369a.png)

コマンドのインプットにはHANA CloudのサービスインスタンスIDとService Managerのキーを渡します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/211f0f0b-b1fe-cb36-17dc-64ad0e9eca7a.png)

また、parametersに以下を設定することで、インスタンスが起動されます。
```json
{
    "data": {
        "serviceStopped": false
    }
}
```

## 実行結果
HANA Cloudが稼働している場合、アウトプットは以下のようになります。HANA Cloudの起動がスキップされています。実行時間は約2秒です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/34b8af5b-e814-f55b-6b45-5c7880bc6a5c.png)

HANA Cloudが停止している場合、アウトプットは以下のようになります。実行時間は3分半でした。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/2e5f9e86-c41f-f125-a849-f7f65b860f51.png)

## スケジュール
14:50～15:15の間、5分おきに起動するようにしたいので、以下のようにスケジュールします。（実際には14:00, 14:05, 14:10, 14:15, 15:50, 15:55にも動きます）

:::note
最終的には、15:10, 15:15, 15:20の3回で十分でした
:::

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/3478d0b7-041c-66f7-5928-5080827d452f.png)

初日の実行結果は以下のようになっていました（初日は20～40分台にもスケジュール）。6:10 UTCに開始したものが時間がかかっており、ここでHANA Cloudが起動したのがわかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/149b4e54-1895-ed50-23d7-3de3fc8780c7.png)

## Appendix: 使い方
### 1. カタログのインポート
以下のカタログをインポートします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/44d55ee7-0434-4e13-b215-3ef50c426f09.png)


```json
{
  "id": "HANACloud-<<<TENANT_ID>>>",
  "technicalName": "HANACloud",
  "name": "Manage HANA Cloud",
  "description": "",
  "owner": "<<<TENANT_ID>>>",
  "inputs": [
    {
      "id": "HANACloud-<<<TENANT_ID>>>:HanaCloudCredentials:1",
      "name": "HanaCloudCredentials",
      "description": null,
      "catalog": "HANACloud-<<<TENANT_ID>>>",
      "owner": null,
      "version": 1,
      "keys": {
        "serviceManagerKey": {
          "type": "object",
          "sensitive": true,
          "description": "Service Manager key"
        },
        "hanaCloudServiceKey": {
          "type": "object",
          "sensitive": true,
          "description": "HANA Cloud service key"
        }
      },
      "values": {
        "serviceManagerKey": "",
        "hanaCloudServiceKey": ""
      },
      "tags": {}
    }
  ],
  "commands": [
    {
      "configuration": {
        "values": [],
        "output": {
          "isHanaCloudAvailable": "$(.pingHanaCloud.output.body | toArray | [.[0] ,.[1] ,.[2]] | any(.value == 1) )",
          "resultHanaStart": "$(.startHanaCloud.executed)",
          "checkPing": "$(.pingHanaCloud.output.body)"
        },
        "executors": [
          {
            "execute": "http-sapcp:HttpRequest:1",
            "input": {
              "responseBodyTransformer": "toObject.data[0].values | reverse",
              "clientId": "$(.execution.input.hanaCloudServiceKey.uaa.clientid)",
              "tokenUrl": "$(.execution.input.hanaCloudServiceKey.uaa.url)/oauth/token?grant_type=client_credentials",
              "method": "GET",
              "clientSecret": "$(.execution.input.hanaCloudServiceKey.uaa.clientsecret)",
              "url": "https://api.gateway.orchestration.$(.execution.input.hanaCloudServiceKey.host | split(\".\") | .[2:] | join(\".\"))/metrics/v1/serviceInstances/$(.execution.input.hanaCloudServiceKey.host | split(\".\")[0])/values?names=HDBAccessible"
            },
            "alias": "pingHanaCloud",
            "description": null,
            "progressMessage": null,
            "initialDelay": null,
            "pause": null,
            "when": null,
            "validate": null,
            "autoRetry": null,
            "repeat": null,
            "errorMessages": [],
            "dryRun": null
          },
          {
            "execute": "sm-sapcp:UpdateServiceInstance:1",
            "input": {
              "instanceId": "$(.execution.input.hanaCloudServiceKey.host | split(\".\")[0])",
              "serviceKey": "$(.execution.input.serviceManagerKey)",
              "parameters": "{     \"data\": {         \"serviceStopped\": false     } }"
            },
            "alias": "startHanaCloud",
            "description": null,
            "progressMessage": null,
            "initialDelay": null,
            "pause": null,
            "when": {
              "semantic": "OR",
              "conditions": [
                {
                  "semantic": "OR",
                  "cases": [
                    {
                      "expression": "$(.pingHanaCloud.output.body | toArray | [.[0] ,.[1] ,.[2]] | any(.value == 1) )",
                      "operator": "EQUALS",
                      "semantic": "OR",
                      "values": [
                        "false"
                      ]
                    }
                  ]
                }
              ]
            },
            "validate": null,
            "autoRetry": null,
            "repeat": null,
            "errorMessages": [],
            "dryRun": null
          }
        ],
        "listeners": []
      },
      "id": "HANACloud-<<<TENANT_ID>>>:CheckHanaCloudAvailability:1",
      "name": "CheckHanaCloudAvailability",
      "description": "Check availability for HANA Cloud",
      "catalog": "HANACloud-<<<TENANT_ID>>>",
      "version": 1,
      "inputKeys": {
        "serviceManagerKey": {
          "type": "object",
          "sensitive": true,
          "required": false,
          "minSize": null,
          "maxSize": null,
          "minValue": null,
          "maxValue": null,
          "allowedValues": null,
          "allowedValuesFromInputKeys": null,
          "suggestedValues": null,
          "suggestedValuesFromInputKeys": null,
          "defaultValue": null,
          "defaultValueFromInput": {
            "inputReference": "HANACloud-<<<TENANT_ID>>>:HanaCloudCredentials:1",
            "inputKey": "serviceManagerKey"
          },
          "description": null
        },
        "hanaCloudServiceKey": {
          "type": "object",
          "sensitive": true,
          "required": false,
          "minSize": null,
          "maxSize": null,
          "minValue": null,
          "maxValue": null,
          "allowedValues": null,
          "allowedValuesFromInputKeys": null,
          "suggestedValues": null,
          "suggestedValuesFromInputKeys": null,
          "defaultValue": null,
          "defaultValueFromInput": {
            "inputReference": "HANACloud-<<<TENANT_ID>>>:HanaCloudCredentials:1",
            "inputKey": "hanaCloudServiceKey"
          },
          "description": null
        }
      },
      "outputKeys": {
        "isHanaCloudAvailable": {
          "type": "string",
          "sensitive": false,
          "description": "Check on whether or not the HANA Cloud instance is available, where: \"true\" = HANA Cloud is available; \"false\" = HANA Cloud is not available."
        },
        "resultHanaStart": {
          "type": "string",
          "sensitive": false,
          "description": null
        },
        "checkPing": {
          "type": "string",
          "sensitive": false,
          "description": "The entire response for the metric \"HDBAccessible\" returned by HANA Cloud Metrics Service REST API."
        }
      },
      "tags": {}
    }
  ]
}
```

### 2. インプットの設定
`HanaCloudCredentials`というインプットにHANA CloudとService Managerのサービスキーを設定してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/d5549382-2b15-7847-4f76-a377acfe512d.png)

### 3. 実行
`CheckHanaCloudAvailability`というコマンドを実行します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/245048a2-023d-0feb-d692-200ee8b6464d.png)

パラメータはインプットから入ってくるので、何も設定せずに"Trigger"を実行します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/1e9c0022-a52d-b9d3-69e8-e1cdf57f167f.png)