Cluster peer dan SVM

Halaman ini menjelaskan cara menyiapkan peering untuk pool penyimpanan ONTAP di Google Cloud NetApp Volumes.

Peering cluster dan peering storage virtual machine (SVM) menghubungkan kumpulan penyimpanan Flex Unified ONTAP Anda ke cluster lain. Hal ini memungkinkan layanan seperti SnapMirror dan FlexCache berjalan.

Gunakan skrip peer_cluster.py untuk menerima penawaran dan memasukkan frasa sandi untuk kumpulan mode ONTAP. Untuk mengetahui informasi selengkapnya, lihat Skrip peering cluster.

Memilih prosedur

Pilih salah satu prosedur peering berikut berdasarkan penyiapan Anda:

  • Peering hybrid: Anda memigrasikan atau mereplikasi dari pool mode ONTAP ke volume NetApp Volumes mode Default (Standard, Premium, Extreme, atau Flex Unified mode Default). NetApp Volumes memulai peering dari tujuan. Anda menyelesaikan peering di kumpulan sumber mode ONTAP.

  • Peering manual: Anda membuat peering sendiri dan tidak menggunakan alur kerja migrasi hibrida atau replikasi eksternal. Pilih skenario:

    • Pool mode ONTAP dan cluster ONTAP eksternal yang ada di lokal atau di luar NetApp Volumes.

    • Dua pool mode ONTAP.

Untuk mengetahui informasi selengkapnya tentang opsi peering tambahan di cluster ONTAP eksternal, lihat dokumentasi NetApp ONTAP.

Cara kerja peering cluster di pool mode ONTAP

Proses peering cluster mengikuti alur kerja penawaran dan penerimaan:

  • Penawaran: satu cluster memulai peering ke alamat IC-LIF peer dan menerima frasa sandi.

  • Terima: cluster peer menyelesaikan peering menggunakan alamat IC-LIF cluster penawaran dan frasa sandi.

Proxy CLI ONTAP tidak interaktif. Perintah cluster peer create -generate-passphrase tidak meminta input, sehingga Anda dapat menjalankan penawaran di pool mode ONTAP menggunakan ontap_execute. Mencantumkan semua alamat IC-LIF jarak jauh sebagai nilai -peer-addrs yang dipisahkan koma. Perintah cluster peer accept akan meminta frasa sandi, sehingga langkah tersebut gagal melalui proxy. Gunakan peer_cluster.py saat pool mode ONTAP harus menerima penawaran.

Untuk membuat peering cluster, gunakan salah satu skenario berikut:

  • Skenario A (ONTAP-mode dan ONTAP eksternal): jalankan cluster peer create-generate-passphrase di pool ONTAP-mode menggunakan ontap_execute dengan semua alamat IC-LIF peer. Administrator ONTAP eksternal menjalankan cluster peer accept di prompt cluster dengan semua alamat IC-LIF mode ONTAP dan memasukkan frasa sandi.

  • Skenario B (dua kumpulan mode ONTAP): jalankan cluster peer create-generate-passphrase di kumpulan A menggunakan ontap_execute, lalu jalankan peer_cluster.py dengan PEER_MODE=accept di kumpulan B.

Lakukan peering SVM dengan menjalankan vserver peer accept menggunakan ontap_execute di pool mode ONTAP. Dalam Skenario A, cluster ONTAP eksternal menjalankan vserver peer create.

Metode replikasi yang memerlukan peering

Metode replikasi berikut memerlukan peering:

  • Sumber ONTAP apa pun ke mode Flex Unified ONTAP (SnapMirror): peer di pool mode ONTAP.

  • Mode ONTAP Terpadu Flex ke tujuan ONTAP mana pun (SnapMirror): peer di pool mode ONTAP.

  • Sumber mode ONTAP ke NetApp Volumes mode Default (migrasi volume atau replikasi eksternal): gunakan peering hybrid di pool sumber mode ONTAP.

Replikasi volume mode default dalam NetApp Volumes tidak menggunakan peering cluster ONTAP.

Peering hybrid

Gunakan bagian ini jika sumbernya adalah pool mode ONTAP dan tujuannya adalah volume mode Default di NetApp Volumes.

Cara kerja NetApp Volumes

Alur kerja peering hybrid mencakup langkah-langkah berikut:

  1. Anda membuat migrasi volume atau replikasi eksternal pada volume tujuan di konsol Google Cloud atau menggunakan Google Cloud CLI. Anda menentukan nama cluster sumber, SVM, volume, dan alamat IC-LIF sumber mode ONTAP.

  2. Jika peering cluster belum dibuat, NetApp Volumes akan memeriksa konektivitas di tujuan, membuat frasa sandi, dan menampilkan petunjuk peering.

  3. Anda harus melakukan langkah-langkah peering yang tersisa di pool sumber mode ONTAP, karena NetApp Volumes tidak menjalankan perintah otomatis di pool sumber Anda.

Anda tidak dapat membatalkan pesanan ini. Anda harus membuat tujuan Mode default terlebih dahulu.

Selesaikan peering cluster pada sumber mode ONTAP

Ambil frasa sandi dan alamat IC-LIF tujuan di konsol Google Cloud pada tab Replikasi atau Migrasi atau menggunakan Google Cloud CLI:

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

Di kumpulan sumber mode ONTAP, terima peering yang ditawarkan NetApp Volumes di tujuan. Siapkan ontap_execute seperti yang dijelaskan dalam Prosedur umum mode ONTAP. Tetapkan POOL_NAME dan LOCATION untuk kumpulan sumber. Cantumkan semua alamat IC-LIF tujuan di PEER_IC_LIF_IPS, lalu jalankan peer_cluster.py dengan 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

Verifikasi konfigurasi di kumpulan sumber:

 ontap_execute "cluster peer show"

Selesaikan peering SVM di sumber mode ONTAP

Jika NetApp Volumes tidak menyelesaikan peering SVM secara otomatis, NetApp Volumes akan menampilkan perintah peer vserver. Jalankan perintah menggunakan ontap_execute, atau terima rekan yang tertunda:

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

Verifikasi konfigurasi:

 ontap_execute "vserver peer show"

Kembali ke alur kerja hybrid di konsol Google Cloud atau jalankan gcloud netapp volumes replications list untuk memverifikasi bahwa replikasi atau migrasi sedang berlangsung.

Peering manual

Gunakan bagian ini saat Anda membuat peering sendiri tanpa alur kerja hibrida, misalnya, sebelum SnapMirror antara pool mode ONTAP dan cluster ONTAP eksternal, atau antara dua pool mode ONTAP.

Sebelum memulai

Selesaikan prasyarat berikut:

  1. Pastikan IC-LIF di pool mode ONTAP dapat menjangkau IC-LIF cluster peer. IC-LIF di kumpulan mode ONTAP berada di IPspace Gcnv; di cluster ONTAP eksternal, IC-LIF berada di IPspace Default. Uji ICMP dan port antar-cluster yang diperlukan.

  2. Siapkan ontap_execute sebelum Anda memulai.

  3. Kumpulkan nama cluster, nama SVM, dan semua alamat IP IC-LIF cluster peer di pool mode ONTAP. Berikan satu alamat per node, yang dipisahkan dengan koma. Jumlah IC-LIF sama dengan jumlah node cluster peer.

  4. Bagikan nama cluster pool, nama SVM, dan semua alamat IC-LIF Anda kepada administrator cluster peer.

Ringkasan

Untuk membuat peering cluster dan SVM secara manual, lakukan langkah-langkah berikut:

  1. Menyiapkan ontap_execute

  2. Mencari nama dan IC-LIF

  3. Memverifikasi konektivitas

  4. Cluster pembanding

  5. SVM Peer

  6. Memverifikasi peering

Mencari nama dan IC-LIF

Di setiap pool mode ONTAP, jalankan perintah pencarian yang dijelaskan dalam Prosedur umum mode ONTAP.

Mencantumkan alamat IC-LIF, yang bergantung pada ukuran kumpulan, dan memberikannya kepada administrator cluster peer.

Memverifikasi konektivitas

Jalankan perintah ping ke IC-LIF dari pool Anda menggunakan flag -vserver Gcnv:

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

Perbaiki masalah perutean atau firewall sebelum Anda melanjutkan. Jika ping gagal di antara kumpulan, lihat Mengapa NetApp Volumes Anda tidak dapat menjangkau Active Directory (dan jaringan lain di belakang VPC Anda) untuk panduan jaringan VPC dan partner.

Cluster pembanding

Untuk membuat peering cluster, gunakan salah satu skenario berikut:

Skenario A: Kumpulan dalam mode ONTAP dan ONTAP eksternal

Untuk menghubungkan pool mode ONTAP dengan cluster ONTAP eksternal, lakukan langkah-langkah berikut:

  1. Penawaran di pool mode ONTAP: jalankan perintah cluster peer create -generate-passphrase menggunakan ontap_execute dengan semua alamat IC-LIF peer. Salin frasa sandi dari output perintah.

  2. Terima di ONTAP eksternal: administrator cluster peer menjalankan perintah cluster peer accept di prompt ONTAP dengan semua alamat IC-LIF mode ONTAP dan memasukkan frasa sandi.

Untuk menawarkan peering di pool mode ONTAP:

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

Cantumkan setiap alamat IC-LIF untuk cluster peer dalam tanda -peer-addrs, yang dipisahkan dengan koma. Tambahkan alamat tambahan jika cluster peer memiliki lebih dari dua node.

ONTAP menampilkan frasa sandi dalam output perintah di bagian Passphrase:. Segera salin frasa sandi; ONTAP memperingatkan bahwa frasa sandi tidak dapat ditampilkan lagi.

Jika perintah gagal atau output tidak menyertakan frasa sandi, gunakan peer_cluster.py dengan PEER_MODE=offer sebagai gantinya. Untuk mengetahui informasi selengkapnya, lihat Skrip peering cluster.

Salin frasa sandi dan kirimkan ke administrator ONTAP eksternal. Terima penawaran dalam periode validitas frasa sandi, yang secara default adalah satu jam.

Terima penawaran di cluster ONTAP eksternal pada perintah cluster peer dengan mencantumkan semua alamat IC-LIF mode ONTAP:

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

Masukkan frasa sandi saat ONTAP meminta Anda. Sertakan setiap alamat IC-LIF dari kumpulan mode ONTAP.

Verifikasi konfigurasi di pool mode ONTAP:

 ontap_execute "cluster peer show"

Skenario B: Dua pool mode ONTAP

Untuk melakukan peering dua pool mode ONTAP, lakukan langkah-langkah berikut:

  1. Penawaran di pool A: jalankan perintah cluster peer create -generate-passphrase dengan menggunakan ontap_execute di pool A. Mencantumkan semua alamat IC-LIF dari pool B di -peer-addrs. Salin frasa sandi.

  2. Terima di pool B: jalankan peer_cluster.py dengan PEER_MODE=accept di pool B, dan cantumkan semua alamat IC-LIF dari pool A di PEER_IC_LIF_IPS.

Penawaran di pool A:

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

Cantumkan setiap IC-LIF dari pool B, yang dipisahkan dengan koma. Tambahkan alamat tambahan jika pool B memiliki lebih dari dua node. Salin nilai Passphrase: dari output perintah.

Jika perintah gagal, gunakan peer_cluster.py dengan PEER_MODE=offer di pool A.

Terima di pool 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

Cantumkan setiap IC-LIF dari pool A di PEER_IC_LIF_IPS.

Verifikasi konfigurasi di setiap pool:

 ontap_execute "cluster peer show"

Konfirmasi bahwa ketersediaan adalah Tersedia dan autentikasi adalah OK.

SVM Peer

Setelah peering cluster berhasil, buat peer SVM untuk SnapMirror. Jika Anda memerlukan FlexCache, tambahkan ke tanda -applications.

Skenario A: Kumpulan dalam mode ONTAP dan ONTAP eksternal

Untuk menghubungkan pool mode ONTAP dengan cluster ONTAP eksternal, lakukan langkah-langkah berikut:

  1. Buat di ONTAP eksternal: administrator cluster peer menjalankan perintah vserver peer create di prompt ONTAP.

  2. Terima di pool mode ONTAP: jalankan perintah vserver peer accept dengan menggunakan ontap_execute.

Buat peer di cluster ONTAP eksternal pada prompt cluster peer:

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

Terima peer di pool mode ONTAP:

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

Skenario B: Dua pool mode ONTAP

Untuk melakukan peering dua pool mode ONTAP, lakukan langkah-langkah berikut:

  1. Buat di pool A: jalankan perintah vserver peer create menggunakan ontap_execute di pool A (atau peer_svm.py jika perintah buat tidak tersedia).

  2. Terima di pool B: jalankan perintah vserver peer accept dengan menggunakan ontap_execute di pool B.

Buat peer di pool A:

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

Terima peer di pool B:

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

Jangan membuat peer SVM yang sama di kedua kumpulan.

Memverifikasi peering

Untuk memverifikasi peering di pool mode ONTAP:

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

Pastikan ketersediaan peer cluster adalah Tersedia dan State peer SVM adalah peered.

Untuk Skenario B, ulangi prosedur di kumpulan kedua. Administrator ONTAP eksternal dapat memverifikasi konfigurasi dengan menjalankan cluster peer show dan vserver peer show di cluster peer.

Jika tidak ada peer cluster, perintah cluster peer show akan menampilkan bahwa entri tidak ada.

Menghapus peering

Hapus peer SVM sebelum peer cluster.

Untuk menghapus peer SVM di satu kumpulan, hapus peer tersebut untuk menghapus hubungan di kedua sisi saat kumpulan dapat dijangkau.

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

Proxy CLI ONTAP tidak mendukung penghapusan peer cluster. Untuk menghapus peer cluster, hapus berdasarkan UUID melalui REST API.

Pertama, cantumkan peer untuk menemukan 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"

Hapus setiap UUID yang ditampilkan:

 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"

Ulangi proses ini di kedua kumpulan mode ONTAP dalam Skenario B. Untuk Skenario A, jalankan cluster peer delete di cluster ONTAP eksternal jika diperlukan.

Skrip peering cluster

Simpan kode berikut sebagai peer_cluster.py. Gunakan di pool mode ONTAP saat pool tersebut harus menerima penawaran peering cluster (PEER_MODE=accept). Peering hybrid dan Skenario B menggunakan mode accept. Gunakan mode offer (PEER_MODE=offer) hanya jika cluster peer create -generate-passphrase gagal melalui ontap_execute atau tidak menampilkan frasa sandi. Skrip ini memerlukan Python 3.8 atau yang lebih baru dan library standar 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()

Variabel lingkungan opsional: EXPIRY_TIME, PEER_APPLICATIONS (default snapmirror), ENCRYPTION_PROPOSED, dan IPSPACE (default Gcnv).

Skrip peering SVM (pengganti)

Gunakan peer_svm.py hanya jika vserver peer create tidak tersedia melalui proxy ONTAP CLI. Sebagai gantinya, gunakan vserver peer create dan vserver peer accept seperti yang dijelaskan dalam SVM Peer.

 #!/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()

Langkah berikutnya