對等互連叢集和 SVM

本頁說明如何為 Google Cloud NetApp Volumes 中的 ONTAP 儲存集區設定對等互連。

叢集對等互連和儲存虛擬機器 (SVM) 對等互連,可將 FlexUnified ONTAP 儲存集區連線至其他叢集。這項功能可讓 SnapMirror 和 FlexCache 等服務運作。

使用 peer_cluster.py 指令碼接受提案,並輸入 ONTAP 模式集區的通關密語。詳情請參閱叢集對等互連指令碼。

選擇程序

請根據您的設定,選擇下列其中一種對等互連程序:

  • 混合對等互連:從 ONTAP 模式集區遷移或複製到 Default 模式的 NetApp Volumes 磁碟區 (Standard、Premium、Extreme 或 Flex Unified Default 模式)。NetApp Volumes 會從目的地開始對接。在 ONTAP 模式來源集區完成對等互連。

  • 手動對等互連:您自行建立對等互連,且未使用混合式遷移或外部複製工作流程。選取情境:

    • ONTAP 模式集區,以及位於地端部署環境或 NetApp Volumes 外部的外部 ONTAP 叢集。

    • 兩個 ONTAP 模式集區。

如要進一步瞭解外部 ONTAP 叢集上的其他對等互連選項,請參閱 NetApp ONTAP 說明文件。

ONTAP 模式集區的叢集對等互連運作方式

叢集對等互連程序會遵循「提供」和「接受」工作流程:

  • 提供:一個叢集會朝對等互連 IC-LIF 位址啟動對等互連,並接收密碼片語。

  • 接受:對等互連叢集使用提供叢集的 IC-LIF 位址和通關密語,完成對等互連。

ONTAP CLI Proxy 並非互動式,cluster peer create -generate-passphrase 指令不會提示輸入內容,因此您可以使用 ontap_execute 在 ONTAP 模式集區上執行方案。以半形逗號分隔的 -peer-addrs 值列出所有遠端 IC-LIF 位址。cluster peer accept 指令會提示輸入密碼片語,因此該步驟會透過 Proxy 失敗。當 ONTAP 模式集區必須接受供應項目時,請使用 peer_cluster.py。

如要對等互連叢集,請使用下列其中一個情境:

  • 情境 A (ONTAP 模式和外部 ONTAP):在 ONTAP 模式集區中執行 cluster peer create-generate-passphrase,並搭配使用 ontap_execute 和所有對等 IC-LIF 位址。外部 ONTAP 管理員會在叢集提示中執行 cluster peer accept,並輸入所有 ONTAP 模式的 IC-LIF 位址和密碼。

  • 情境 B (兩個 ONTAP 模式集區):使用 ontap_execute 在集區 A 上執行 cluster peer create-generate-passphrase,然後在集區 B 上執行 peer_cluster.py (使用 PEER_MODE=accept)。

在 ONTAP 模式集區中執行 ontap_execute,藉此執行 SVM 對等互連 vserver peer accept。在情境 A 中,外部 ONTAP 叢集會執行 vserver peer create。

需要對等互連的複製方法

下列複製方法需要對等互連:

  • 任何 ONTAP 來源到 Flex Unified ONTAP 模式 (SnapMirror):在 ONTAP 模式集區上對等互連。

  • 將 Flex Unified ONTAP 模式對等至任何 ONTAP 目的地 (SnapMirror):在 ONTAP 模式集區上對等。

  • ONTAP 模式來源到預設模式 NetApp Volumes (磁碟區遷移或外部複製):在 ONTAP 模式來源集區上使用混合對等互連。

NetApp Volumes 內的預設模式磁碟區複製作業不會使用 ONTAP 叢集對等互連。

混合對等互連

如果來源是 ONTAP 模式集區,目的地是 NetApp Volumes 中的預設模式磁碟區,請使用本節內容。

NetApp Volumes 的運作方式

混合對等互連工作流程包含下列步驟:

  1. 您可以在 Google Cloud 控制台中,或使用 Google Cloud CLI,在目的地磁碟區上建立磁碟區遷移或外部複製作業。您指定 ONTAP 模式的來源叢集名稱、SVM、磁碟區和來源 IC-LIF 位址。

  2. 如果尚未建立叢集對等互連,NetApp Volumes 會檢查目的地連線能力,產生通關密語,並顯示對等互連操作說明。

  3. 您必須在 ONTAP 模式的來源集區中執行其餘對等互連步驟,因為 NetApp Volumes 不會在來源集區中執行自動化指令。

這項操作無法復原。您必須先建立「預設模式」目的地。

在 ONTAP 模式來源完成叢集對接

在 Google Cloud 控制台的「Replication」或「Migration」分頁中,或使用 Google Cloud CLI 擷取密碼片語和目的地 IC-LIF 位址:

 gcloud netapp volumes replications list --volume=DESTINATION_VOLUME --location=LOCATION \
  --format="table(hybridPeeringDetails.command,hybridPeeringDetails.passphrase,hybridPeeringDetails.subnetIp)"

在 ONTAP 模式來源集區中,接受 NetApp Volumes 在目的地提供的對等互連。如常見 ONTAP 模式程序所述,設定 ontap_execute。為來源集區設定 POOL_NAME 和 LOCATION。在 PEER_IC_LIF_IPS 中列出所有目的地 IC-LIF 位址,然後執行 peer_cluster.py (使用 PEER_MODE=accept):

 export PROJECT_ID="myproject"
 export LOCATION="us-central1-a"
 export POOL_NAME="ontap-mode-source-pool"
 export PEER_MODE="accept"
 export PEER_IC_LIF_IPS="DESTINATION_IC_LIF_IP_1,DESTINATION_IC_LIF_IP_2"
 export PASSPHRASE="passphrase-from-gcnv-console"

 python3 peer_cluster.py

驗證來源集區的設定:

 ontap_execute "cluster peer show"

在 ONTAP 模式來源上完成 SVM 對等互連

如果 NetApp Volumes 未自動完成 SVM 對等互連,系統會顯示 vserver 對等互連指令。使用 ontap_execute 執行指令,或接受待處理的對等互連:

 ontap_execute "vserver peer accept -vserver SOURCE_SVM_NAME -peer-vserver PEER_SVM_NAME"

驗證設定:

 ontap_execute "vserver peer show"

返回 Google Cloud 控制台中的混合式工作流程,或執行 gcloud netapp volumes replications list,確認複製或遷移作業正在進行中。

手動對等互連

如果您要自行建立對等互連,且不使用混合式工作流程,例如在 ONTAP 模式集區與外部 ONTAP 叢集之間,或在兩個 ONTAP 模式集區之間使用 SnapMirror 之前,請使用本節內容。

事前準備

完成下列必要條件:

  1. 確認 ONTAP 模式集區的 IC-LIF 可連上對等叢集的 IC-LIF。 ONTAP 模式集區的 IC-LIF 位於 IPspace Gcnv,外部 ONTAP 叢集的 IC-LIF 則位於 IPspace Default。測試 ICMP 和叢集間所需的通訊埠。

  2. 開始前,請先設定 ontap_execute。

  3. 在 ONTAP 模式集區中,收集叢集名稱、SVM 名稱和所有對等叢集 IC-LIF IP 位址。請以半形逗號分隔各節點的地址。IC-LIF 數量與對等叢集節點數量相符。

  4. 將集區的叢集名稱、SVM 名稱和所有 IC-LIF 位址分享給對等叢集管理員。

總覽

如要手動對等互連叢集和 SVM,請執行下列步驟:

  1. 設定 ontap_execute

  2. 查詢名稱和 IC-LIF

  3. 確認連線

  4. 同業叢集

  5. 同業 SVM

  6. 驗證對等互連

查詢名稱和 IC-LIF

在每個 ONTAP 模式集區中,執行「常見 ONTAP 模式程序」一文所述的查閱指令。

列出 IC-LIF 位址 (取決於集區大小),並提供給對等叢集管理員。

確認連線

使用 -vserver Gcnv 旗標,從集區對等互連 IC-LIF 執行 ping 指令:

 ontap_execute "network ping -vserver Gcnv -destination PEER_IC_LIF_IP -lif intercluster-1"

請先修正路由或防火牆問題再繼續。如果集區之間無法執行 Ping,請參閱「Why your NetApp Volumes can't reach Active Directory (and other networks behind your VPC)」,瞭解虛擬私有雲和合作夥伴網路的相關指引。

對等互連叢集

如要對等互連叢集,請使用下列其中一個情境:

情境 A:ONTAP 模式集區和外部 ONTAP

如要將 ONTAP 模式集區與外部 ONTAP 叢集對等互連,請執行下列步驟:

  1. ONTAP 模式集區的優惠:使用 ontap_execute 搭配所有對等 IC-LIF 位址,執行 cluster peer create -generate-passphrase 指令。從指令輸出內容複製密碼片語。

  2. 在外部 ONTAP 上接受:對等叢集管理員在 ONTAP 提示字元中執行 cluster peer accept 指令,並輸入所有 ONTAP 模式 IC-LIF 位址和密碼。

如要在 ONTAP 模式集區提供對等互連:

 ontap_execute "cluster peer create -ipspace Gcnv -peer-addrs PEER_IC_LIF_1,PEER_IC_LIF_2 -generate-passphrase"

在 -peer-addrs 旗標中列出對等叢集的每個 IC-LIF 位址,並以半形逗號分隔。如果對等互連叢集有超過兩個節點,請新增其他位址。

ONTAP 會在指令輸出內容的 Passphrase: 下方顯示密碼片語。立即複製密碼片語;ONTAP 會警告您無法再次顯示密碼片語。

如果指令失敗或輸出內容未包含密碼片語,請改用 PEER_MODE=offer 執行 peer_cluster.py。詳情請參閱「叢集對等互連指令碼」。

複製密語並傳送給外部 ONTAP 管理員。在通關密語有效期限內接受邀請,預設為一小時。

在對等叢集提示中接受外部 ONTAP 叢集的提議,方法是列出所有 ONTAP 模式 IC-LIF 位址:

 cluster peer accept -ipspace Default -peer-addrs ONTAP_MODE_IC_LIF_1,ONTAP_MODE_IC_LIF_2

ONTAP 提示時,請輸入通關密語。請納入 ONTAP 模式集區中的所有 IC-LIF 位址。

驗證 ONTAP 模式集區的設定:

 ontap_execute "cluster peer show"

情境 B:兩個 ONTAP 模式集區

如要對等互連兩個 ONTAP 模式集區,請執行下列步驟:

  1. 在集區 A 上提供:使用集區 A 上的 ontap_execute 執行 cluster peer create -generate-passphrase 指令。在 -peer-addrs 中列出集區 B 的所有 IC-LIF 位址。複製通關密語。

  2. 在集區 B 上接受:在集區 B 上執行 peer_cluster.py,並在 PEER_IC_LIF_IPS 中列出集區 A 的所有 IC-LIF 位址。PEER_MODE=accept

A 組優惠:

 ontap_execute "cluster peer create -ipspace Gcnv -peer-addrs POOL_B_IC_LIF_1,POOL_B_IC_LIF_2 -generate-passphrase"

列出集區 B 中的每個 IC-LIF,並以半形逗號分隔。如果集區 B 有兩個以上的節點,請新增其他位址。從指令輸出內容複製 Passphrase: 值。

如果指令失敗,請在集區 A 中使用 peer_cluster.py 和 PEER_MODE=offer。

在集區 B 中接受:

 export PROJECT_ID="myproject"
 export LOCATION="us-west1-b"
 export POOL_NAME="pool-b"
 export PEER_MODE="accept"
 export PEER_IC_LIF_IPS="POOL_A_IC_LIF_1,POOL_A_IC_LIF_2"
 export PASSPHRASE="passphrase-from-pool-a"

 python3 peer_cluster.py

列出集區 A 中的每個 IC-LIF,如 PEER_IC_LIF_IPS 所示。

驗證每個集區的設定:

 ontap_execute "cluster peer show"

確認「可用性」為「可用」,且驗證狀態為「OK」。

對等互連 SVM

叢集對等互連成功後,請為 SnapMirror 建立 SVM 對等互連。如需 FlexCache,請將其新增至 -applications 旗標。

情境 A:ONTAP 模式集區和外部 ONTAP

如要將 ONTAP 模式集區與外部 ONTAP 叢集對等互連,請執行下列步驟:

  1. 在外部 ONTAP 上建立:對等叢集管理員會在 ONTAP 提示中執行 vserver peer create 指令。

  2. 在 ONTAP 模式集區中接受:使用 ontap_execute 執行 vserver peer accept 指令。

在外部 ONTAP 叢集的對等互連叢集提示中建立對等互連:

 vserver peer create -vserver PEER_SVM_NAME -peer-vserver ONTAP_MODE_SVM_NAME \
  -peer-cluster ONTAP_MODE_CLUSTER_NAME -applications snapmirror

在 ONTAP 模式集區中接受對等互連:

 ontap_execute "vserver peer accept -vserver ONTAP_MODE_SVM_NAME -peer-vserver PEER_SVM_NAME"

情境 B:兩個 ONTAP 模式集區

如要對等互連兩個 ONTAP 模式集區,請執行下列步驟:

  1. 在集區 A 上建立:在集區 A 上使用 ontap_execute 執行 vserver peer create 指令 (如果無法使用建立指令,請使用 peer_svm.py)。

  2. 在集區 B 上接受:在集區 B 上使用 ontap_execute 執行 vserver peer accept 指令。

在集區 A 上建立對等互連:

 ontap_execute "vserver peer create -vserver POOL_A_SVM -peer-vserver POOL_B_SVM -peer-cluster POOL_B_CLUSTER_NAME -applications snapmirror"

在集區 B 上接受對等互連:

 ontap_execute "vserver peer accept -vserver POOL_B_SVM -peer-vserver POOL_A_SVM"

請勿在兩個集區中建立相同的 SVM 對等互連。

驗證對等互連

如要驗證 ONTAP 模式集區的對等互連:

 ontap_execute "cluster peer show"
 ontap_execute "vserver peer show"

確認叢集對等互連可用性為「Available」,且 SVM 對等互連狀態為「peered」。

如果是情境 B,請在第二個集區重複上述程序。外部 ONTAP 管理員可以在對等互連叢集上執行 cluster peer show 和 vserver peer show,驗證設定。

如果沒有叢集對等互連,cluster peer show 指令會傳回項目不存在。

刪除對等互連

請先刪除 SVM 對等互連,再刪除叢集對等互連。

如要刪除一個集區的 SVM 對等互連,請刪除該對等互連,以便在集區可連線時,移除雙方的關係。

 ontap_execute "vserver peer delete -vserver LOCAL_SVM_NAME -peer-vserver PEER_SVM_NAME"

ONTAP CLI Proxy 不支援刪除叢集對等互連。如要刪除叢集對等互連,請透過 REST API 依 UUID 刪除。

首先,列出對等互連項目,找出 UUID:

 TOKEN=$(gcloud auth print-access-token)
 curl -s -H "Authorization: Bearer $TOKEN" \

 "https://netapp.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/storagePools/POOL_NAME/ontap/api/cluster/peers"

刪除每個傳回的 UUID:

 curl -s -X DELETE -H "Authorization: Bearer $TOKEN" \

 "https://netapp.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/storagePools/POOL_NAME/ontap/api/cluster/peers/PEER_UUID"

在情境 B 中,對兩個 ONTAP 模式集區重複執行這個程序。如為情境 A,請視需要對外部 ONTAP 叢集執行 cluster peer delete。

叢集對等互連指令碼

將下列內容儲存為 peer_cluster.py。如果集區必須接受叢集對等互連邀請 (PEER_MODE=accept),請在 ONTAP 模式集區中使用。混合式對等互連和情境 B 使用 accept 模式。只有在 cluster peer create -generate-passphrase 透過 ontap_execute 失敗或未傳回通關密語時,才使用 offer 模式 (PEER_MODE=offer)。這項指令碼需要 Python 3.8 以上版本和 Python 標準程式庫。

 #!/usr/bin/env python3
"""Cluster peer offer or accept on an ONTAP-mode pool through the NetApp Volumes REST proxy."""

import json
import os
import subprocess
import sys
import time
import urllib.error
import urllib.request

REQUIRED = ("PROJECT_ID", "LOCATION", "POOL_NAME", "PEER_MODE")
missing = [name for name in REQUIRED if not os.environ.get(name)]
if missing:
    sys.exit(f"Set required environment variables: {', '.join(missing)}")

PROJECT_ID = os.environ["PROJECT_ID"]
LOCATION = os.environ["LOCATION"]
POOL_NAME = os.environ["POOL_NAME"]
PEER_MODE = os.environ["PEER_MODE"].lower()
POLL_INTERVAL = int(os.environ.get("POLL_INTERVAL", "10"))

API_ROOT = (
    f"https://netapp.googleapis.com/v1/projects/{PROJECT_ID}"
    f"/locations/{LOCATION}/storagePools/{POOL_NAME}/ontap/api"
)

def parse_ip_list(value: str) -> list:
    return [ip.strip() for ip in value.replace("#", ",").split(",") if ip.strip()]

def get_access_token() -> str:
    return subprocess.check_output(
        ["gcloud", "auth", "print-access-token"], text=True
    ).strip()

def api_request(method: str, path: str, body=None) -> dict:
    headers = {
        "Authorization": f"Bearer {get_access_token()}",
        "Content-Type": "application/json",
    }
    data = json.dumps(body).encode() if body is not None else None
    req = urllib.request.Request(f"{API_ROOT}{path}", data=data, headers=headers, method=method)
    try:
        with urllib.request.urlopen(req) as resp:
            return json.loads(resp.read())
    except urllib.error.HTTPError as exc:
        detail = exc.read().decode()
        sys.exit(f"API request failed ({exc.code}): {detail}")

def job_record(response: dict) -> dict:
    return response.get("rawResponse") or response.get("body") or response

def job_state(response: dict) -> str:
    return job_record(response).get("state", "")

def format_job_error(response: dict) -> str:
    record = job_record(response)
    message = record.get("message") or record.get("error", {}).get("message")
    if message:
        return message
    return json.dumps(response, indent=2)

def wait_for_job(job_uuid: str) -> None:
    print(f"ONTAP job UUID: {job_uuid}")
    while True:
        response = api_request("GET", f"/cluster/jobs/{job_uuid}")
        state = job_state(response)
        print(f"  Job state: {state}")
        if state == "success":
            return
        if state in ("failure", "error"):
            sys.exit(f"Job failed: {format_job_error(response)}")
        time.sleep(POLL_INTERVAL)

def extract_passphrase(response: dict) -> str:
    body = response.get("body") or response.get("rawResponse") or {}
    records = body.get("records") or []
    if records:
        auth = records[0].get("authentication") or {}
        if auth.get("passphrase"):
            return auth["passphrase"]
    auth = body.get("authentication") or {}
    return auth.get("passphrase", "")

def build_offer_body() -> dict:
    if not os.environ.get("PEER_IC_LIF_IPS"):
        sys.exit("Set PEER_IC_LIF_IPS for offer mode")

    payload = {
        "remote": {"ip_addresses": parse_ip_list(os.environ["PEER_IC_LIF_IPS"])},
        "authentication": {"generate_passphrase": True},
        "ipspace": {"name": os.environ.get("IPSPACE", "Gcnv")},
    }

    expiry = os.environ.get("EXPIRY_TIME")
    if expiry:
        payload["authentication"]["expiry_time"] = expiry

    local_svm = os.environ.get("LOCAL_SVM_NAME")
    if local_svm:
        payload["initial_allowed_svms"] = [{"name": local_svm}]

    apps = os.environ.get("PEER_APPLICATIONS", "snapmirror")
    payload["peer_applications"] = [a.strip() for a in apps.split(",") if a.strip()]

    encryption = os.environ.get("ENCRYPTION_PROPOSED")
    if encryption:
        payload["encryption"] = {"proposed": encryption}

    return {"body": payload}

def build_accept_body() -> dict:
    for name in ("PEER_IC_LIF_IPS", "PASSPHRASE"):
        if not os.environ.get(name):
            sys.exit(f"Set {name} for accept mode")

    return {
        "body": {
            "remote": {"ip_addresses": parse_ip_list(os.environ["PEER_IC_LIF_IPS"])},
            "authentication": {"passphrase": os.environ["PASSPHRASE"]},
            "ipspace": {"name": os.environ.get("IPSPACE", "Gcnv")},
        }
    }

def main() -> None:
    if PEER_MODE == "offer":
        body = build_offer_body()
        print("Creating cluster peer offer...")
    elif PEER_MODE == "accept":
        body = build_accept_body()
        print("Accepting cluster peer offer...")
    else:
        sys.exit("PEER_MODE must be 'offer' or 'accept'")

    response = api_request("POST", "/cluster/peers?return_records=true", body)

    job = (response.get("body") or {}).get("job") or response.get("job")
    if job and job.get("uuid"):
        wait_for_job(job["uuid"])

    if PEER_MODE == "offer":
        passphrase = extract_passphrase(response)
        if passphrase:
            print(f"\nPassphrase (provide to the peer pool): {passphrase}")
        else:
            print(json.dumps(response, indent=2))

if __name__ == "__main__":
    main()

選用環境變數:EXPIRY_TIME、PEER_APPLICATIONS (預設值 snapmirror)、ENCRYPTION_PROPOSED 和 IPSPACE (預設值 Gcnv)。

SVM 對等互連指令碼 (備用)

只有在無法透過 ONTAP CLI Proxy 使用 vserver peer create 時,才使用 peer_svm.py。請改用對等 SVM 中所述的 vserver peer create 和 vserver peer accept。

 #!/usr/bin/env python3
"""Create an SVM peer on an ONTAP-mode pool (fallback when vserver peer create is unavailable)."""

import json
import os
import subprocess
import sys
import time
import urllib.error
import urllib.request

REQUIRED = (
    "PROJECT_ID", "LOCATION", "POOL_NAME",
    "LOCAL_SVM_NAME", "PEER_CLUSTER_NAME", "PEER_SVM_NAME",
)
missing = [name for name in REQUIRED if not os.environ.get(name)]
if missing:
    sys.exit(f"Set required environment variables: {', '.join(missing)}")

PROJECT_ID = os.environ["PROJECT_ID"]
LOCATION = os.environ["LOCATION"]
POOL_NAME = os.environ["POOL_NAME"]
LOCAL_SVM_NAME = os.environ["LOCAL_SVM_NAME"]
PEER_CLUSTER_NAME = os.environ["PEER_CLUSTER_NAME"]
PEER_SVM_NAME = os.environ["PEER_SVM_NAME"]
APPLICATIONS = [
    app.strip()
    for app in os.environ.get("APPLICATIONS", "snapmirror").split(",")
    if app.strip()
]
POLL_INTERVAL = int(os.environ.get("POLL_INTERVAL", "10"))

API_ROOT = (
    f"https://netapp.googleapis.com/v1/projects/{PROJECT_ID}"
    f"/locations/{LOCATION}/storagePools/{POOL_NAME}/ontap/api"
)

def get_access_token() -> str:
    return subprocess.check_output(
        ["gcloud", "auth", "print-access-token"], text=True
    ).strip()

def api_request(method: str, path: str, body=None) -> dict:
    headers = {
        "Authorization": f"Bearer {get_access_token()}",
        "Content-Type": "application/json",
    }
    data = json.dumps(body).encode() if body is not None else None
    req = urllib.request.Request(f"{API_ROOT}{path}", data=data, headers=headers, method=method)
    try:
        with urllib.request.urlopen(req) as resp:
            return json.loads(resp.read())
    except urllib.error.HTTPError as exc:
        sys.exit(f"API request failed ({exc.code}): {exc.read().decode()}")

def format_job_error(response: dict) -> str:
    record = response.get("rawResponse") or response.get("body") or response
    message = record.get("message") or record.get("error", {}).get("message")
    return message or json.dumps(response, indent=2)

def wait_for_job(job_uuid: str) -> None:
    while True:
        response = api_request("GET", f"/cluster/jobs/{job_uuid}")
        record = response.get("rawResponse") or response.get("body") or response
        state = record.get("state", "")
        print(f"  Job state: {state}")
        if state == "success":
            print("SVM peer relationship created.")
            return
        if state in ("failure", "error"):
            sys.exit(f"Job failed: {format_job_error(response)}")
        time.sleep(POLL_INTERVAL)

def main() -> None:
    body = {
        "body": {
            "svm": {"name": LOCAL_SVM_NAME},
            "peer": {
                "cluster": {"name": PEER_CLUSTER_NAME},
                "svm": {"name": PEER_SVM_NAME},
            },
            "applications": APPLICATIONS,
        }
    }
    response = api_request("POST", "/svm/peers?return_records=true", body)
    job = (response.get("body") or {}).get("job") or response.get("job")
    if not job or not job.get("uuid"):
        print(json.dumps(response, indent=2))
        return
    wait_for_job(job["uuid"])

if __name__ == "__main__":
    main()

後續步驟