Intercambio de tráfico entre clústeres y SVM

En esta página, se describe cómo configurar el peering para el grupo de almacenamiento de ONTAP en Google Cloud NetApp Volumes.

El intercambio de tráfico entre clústeres y el intercambio de tráfico entre máquina virtual de almacenamiento (SVM) conectan tu grupo de almacenamiento Flex Unified ONTAP a otro clúster. Esto permite que se ejecuten servicios como SnapMirror y FlexCache.

Usa la secuencia de comandos peer_cluster.py para aceptar una oferta y, luego, ingresa una frase de contraseña para un grupo en modo ONTAP. Para obtener más información, consulta Secuencia de comandos de interconexión de clústeres.

Elige un procedimiento

Elige uno de los siguientes procedimientos de interconexión según tu configuración:

  • Interconexión híbrida: Migras o replicas desde un grupo en modo ONTAP a un volumen de NetApp Volumes en modo predeterminado (Standard, Premium, Extreme o Flex Unified en modo predeterminado). NetApp Volumes comienza el intercambio de tráfico desde el destino. Completas el peering en el grupo de origen en modo ONTAP.

  • Interconexión manual: Estableces la interconexión por tu cuenta y no usas un flujo de trabajo de migración híbrida o replicación externa. Selecciona una situación:

    • Un grupo en modo ONTAP y un clúster ONTAP externo que se encuentra en las instalaciones o fuera de NetApp Volumes

    • Dos grupos en modo ONTAP

Para obtener más información sobre las opciones de peering adicionales en el clúster externo de ONTAP, consulta la documentación de NetApp ONTAP.

Cómo funciona el intercambio de tráfico entre clústeres en un grupo en modo ONTAP

El proceso de intercambio de tráfico entre clústeres sigue un flujo de trabajo de oferta y aceptación:

  • Oferta: Un clúster inicia el peering hacia las direcciones IC-LIF del par y recibe una frase de contraseña.

  • Aceptar: El clúster de procesamiento completo el intercambio de tráfico con las direcciones IC-LIF y la frase de contraseña del clúster de oferta.

El proxy de la CLI de ONTAP no es interactivo. El comando cluster peer create -generate-passphrase no solicita entrada, por lo que puedes ejecutar la oferta en un grupo en modo ONTAP con ontap_execute. Enumera todas las direcciones IC-LIF remotas como un valor -peer-addrs separado por comas. El comando cluster peer accept solicita la frase de contraseña, por lo que ese paso falla a través del proxy. Usa peer_cluster.py cuando un grupo en modo ONTAP debe aceptar una oferta.

Para interconectar los clústeres, usa una de las siguientes situaciones:

  • Situación A (ONTAP en modo y ONTAP externo): Ejecuta cluster peer create-generate-passphrase en el grupo de ONTAP en modo con ontap_execute y todas las direcciones IC-LIF de socios. El administrador externo de ONTAP ejecuta cluster peer accept en el símbolo del sistema del clúster con todas las direcciones de IC-LIF en modo ONTAP y, luego, ingresa la frase de contraseña.

  • Situación B (dos grupos en modo ONTAP): Ejecuta cluster peer create-generate-passphrase en el grupo A con ontap_execute y, luego, ejecuta peer_cluster.py con PEER_MODE=accept en el grupo B.

Realiza el intercambio de tráfico entre SVM ejecutando vserver peer accept con ontap_execute en el grupo en modo ONTAP. En la situación A, el clúster ONTAP externo ejecuta vserver peer create.

Métodos de replicación que requieren peering

Los siguientes métodos de replicación requieren peering:

  • Cualquier origen de ONTAP a Flex Unified en modo ONTAP (SnapMirror): Peer en el grupo en modo ONTAP.

  • Modo ONTAP unificado de Flex a cualquier destino de ONTAP (SnapMirror): Peer en el grupo en modo ONTAP.

  • Fuente en modo ONTAP a NetApp Volumes en modo predeterminado (migración de volúmenes o replicación externa): Usa el peering híbrido en el grupo de origen en modo ONTAP.

La replicación de volúmenes en modo predeterminado dentro de NetApp Volumes no usa el intercambio de tráfico del clúster ONTAP.

Interconexión híbrida

Usa esta sección cuando la fuente sea un grupo en modo ONTAP y el destino sea un volumen en modo predeterminado en NetApp Volumes.

Cómo funciona NetApp Volumes

El flujo de trabajo de la vinculación híbrida incluye los siguientes pasos:

  1. Crea una migración de volúmenes o una replicación externa en el volumen de destino en la consola de Google Cloud o con Google Cloud CLI. Especificas el nombre del clúster de origen en modo ONTAP, la SVM, el volumen y las direcciones IC-LIF de origen.

  2. Si aún no se estableció la interconexión de clústeres, NetApp Volumes verifica la conectividad en el destino, genera una frase de contraseña y muestra las instrucciones de interconexión.

  3. Debes realizar los pasos de interconexión restantes en tu grupo de origen en modo ONTAP, ya que NetApp Volumes no ejecuta comandos automatizados en tu grupo de origen.

No puedes revertir este pedido. Primero debes crear el destino en modo predeterminado.

Completa el intercambio de tráfico entre clústeres en la fuente en modo ONTAP

Recupera la frase de contraseña y las direcciones IC-LIF de destino en la consola de Google Cloud en la pestaña Replication o Migration, o bien usa Google Cloud CLI:

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

En el grupo de origen en modo ONTAP, acepta el peering que NetApp Volumes ofreció en el destino. Configura ontap_execute como se describe en Procedimientos comunes en modo ONTAP. Establece POOL_NAME y LOCATION para el grupo de origen. Enumera todas las direcciones de IC-LIF de destino en PEER_IC_LIF_IPS y, luego, ejecuta peer_cluster.py con 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

Verifica la configuración en el grupo de origen:

 ontap_execute "cluster peer show"

Completa el intercambio de tráfico entre SVM en la fuente en modo ONTAP

Si NetApp Volumes no finaliza la interconexión de SVM automáticamente, muestra un comando de interconexión de vserver. Ejecuta el comando con ontap_execute o acepta el par pendiente:

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

Verifica la configuración:

 ontap_execute "vserver peer show"

Vuelve al flujo de trabajo híbrido en la consola de Google Cloud o ejecutagcloud netapp volumes replications listpara verificar que la replicación o la migración estén en curso.

Interconexión manual

Usa esta sección cuando establezcas el peering por tu cuenta sin un flujo de trabajo híbrido, por ejemplo, antes de SnapMirror entre un grupo en modo ONTAP y un clúster ONTAP externo, o entre dos grupos en modo ONTAP.

Antes de comenzar

Completa los siguientes requisitos previos:

  1. Confirma que las IC-LIF del grupo en modo ONTAP lleguen a las IC-LIF del clúster de par. Las IC-LIF en grupos en modo ONTAP se encuentran en el espacio IP Gcnv; en los clústeres ONTAP externos, se encuentran en el espacio IP Default. Prueba los puertos ICMP y los puertos entre clústeres necesarios.

  2. Configura ontap_execute antes de comenzar.

  3. Recopila el nombre del clúster, el nombre de la SVM y todas las direcciones IP de IC-LIF del clúster peer en el grupo en modo ONTAP. Proporciona una dirección por nodo, separada por comas. La cantidad de IC-LIF coincide con la cantidad de nodos del clúster de procesamiento.

  4. Comparte el nombre del clúster, el nombre de la SVM y todas las direcciones de IC-LIF de tu grupo con el administrador del clúster peer.

Descripción general

Para interconectar tus clústeres y SVM de forma manual, sigue estos pasos:

  1. Cómo configurar ontap_execute

  2. Cómo buscar nombres y IC-LIF

  3. Verifica la conectividad

  4. Clústeres de empresas similares

  5. SVMs de empresas similares

  6. Verifica el intercambio de tráfico

Cómo buscar nombres y IC-LIF

En cada grupo en modo ONTAP, ejecuta los comandos de búsqueda que se describen en Procedimientos comunes en modo ONTAP.

Enumera las direcciones de IC-LIF, que dependen del tamaño del grupo, y proporciónalas al administrador del clúster peer.

Verifica la conectividad

Ejecuta un comando ping para hacer ping a las IC-LIF de tu grupo con la marca -vserver Gcnv:

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

Soluciona los problemas de enrutamiento o firewall antes de continuar. Si la prueba de ping falla entre los grupos, consulta Por qué tus NetApp Volumes no pueden acceder a Active Directory (y a otras redes detrás de tu VPC) para obtener orientación sobre la VPC y la red de socios.

Clústeres de empresas similares

Para interconectar los clústeres, usa una de las siguientes situaciones:

Situación A: Grupo en modo ONTAP y ONTAP externo

Para interconectar un grupo en modo ONTAP con un clúster ONTAP externo, realiza los siguientes pasos:

  1. Oferta en el grupo en modo ONTAP: Ejecuta el comando cluster peer create -generate-passphrase con ontap_execute y todas las direcciones IC-LIF de los pares. Copia la frase de contraseña del resultado del comando.

  2. Aceptar en el ONTAP externo: El administrador del clúster peer ejecuta el comando cluster peer accept en el símbolo del sistema de ONTAP con todas las direcciones de IC-LIF en modo ONTAP y, luego, ingresa la frase de contraseña.

Para ofrecer peering en el grupo en modo ONTAP, haz lo siguiente:

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

Enumera todas las direcciones de IC-LIF para el clúster de la misma entidad en la marca -peer-addrs, separadas por comas. Agrega direcciones adicionales si el clúster de socios tiene más de dos nodos.

ONTAP muestra la frase de contraseña en el resultado del comando en Passphrase:. Copia la frase de contraseña de inmediato. ONTAP advierte que no puede volver a mostrarla.

Si el comando falla o el resultado no incluye una frase de contraseña, usa peer_cluster.py con PEER_MODE=offer. Para obtener más información, consulta Secuencia de comandos de interconexión de clústeres.

Copia la frase de contraseña y envíala al administrador de ONTAP externo. Acepta la oferta dentro del período de validez de la frase de contraseña, que es de una hora de forma predeterminada.

Acepta la oferta en el clúster ONTAP externo en la solicitud del clúster peer enumerando todas las direcciones de IC-LIF en modo ONTAP:

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

Ingresa la frase de contraseña cuando ONTAP te lo solicite. Incluye todas las direcciones de IC-LIF del grupo en modo ONTAP.

Verifica la configuración en el grupo en modo ONTAP:

 ontap_execute "cluster peer show"

Situación B: Dos grupos en modo ONTAP

Para interconectar dos grupos en modo ONTAP, sigue estos pasos:

  1. Oferta en el grupo A: Ejecuta el comando cluster peer create -generate-passphrase con ontap_execute en el grupo A. Enumera todas las direcciones de IC-LIF del grupo B en -peer-addrs. Copia la frase de contraseña.

  2. Accept on pool B: Ejecuta peer_cluster.py con PEER_MODE=accept en el grupo B y enumera todas las direcciones de IC-LIF del grupo A en PEER_IC_LIF_IPS.

Oferta en la piscina A:

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

Enumera cada IC-LIF del grupo B, separada por comas. Agrega direcciones adicionales cuando el grupo B tenga más de dos nodos. Copia el valor de Passphrase: del resultado del comando.

Si el comando falla, usa peer_cluster.py con PEER_MODE=offer en el grupo A.

Acepta en el grupo 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

Enumera cada IC-LIF del grupo A en PEER_IC_LIF_IPS.

Verifica la configuración en cada grupo:

 ontap_execute "cluster peer show"

Confirma que la disponibilidad sea Available y que la autenticación sea OK.

SVM de intercambio de tráfico

Después de que el peering de clústeres se realice correctamente, crea un peer de SVM para SnapMirror. Si necesitas FlexCache, agrégalo a la marca -applications.

Situación A: Grupo en modo ONTAP y ONTAP externo

Para interconectar un grupo en modo ONTAP con un clúster ONTAP externo, realiza los siguientes pasos:

  1. Create on external ONTAP: El administrador del clúster de procesamiento ejecuta el comando vserver peer create en el símbolo del sistema de ONTAP.

  2. Aceptar en el grupo de modo ONTAP: Ejecuta el comando vserver peer accept con ontap_execute.

Crea el par en el clúster externo de ONTAP en el mensaje del clúster par:

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

Acepta el par en el grupo en modo ONTAP:

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

Situación B: Dos grupos en modo ONTAP

Para interconectar dos grupos en modo ONTAP, sigue estos pasos:

  1. Create on pool A: Ejecuta el comando vserver peer create con ontap_execute en el grupo A (o peer_svm.py si el comando create no está disponible).

  2. Aceptar en el grupo B: Ejecuta el comando vserver peer accept con ontap_execute en el grupo B.

Crea el par en el grupo A:

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

Acepta el intercambio de tráfico en el grupo B:

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

No crees el mismo par de SVM en ambos grupos.

Verifica el intercambio de tráfico

Para verificar el intercambio de tráfico en el grupo en modo ONTAP, haz lo siguiente:

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

Confirma que la disponibilidad del clúster de intercambio de tráfico sea Available y que el estado del SVM de intercambio de tráfico sea peered.

Para el escenario B, repite el procedimiento en la segunda piscina. El administrador externo de ONTAP puede verificar la configuración ejecutando cluster peer show y vserver peer show en el clúster de procesamiento.

Cuando no existen nodos de clúster, el comando cluster peer show devuelve que la entrada no existe.

Borra el intercambio de tráfico

Borra los pares de SVM antes que los pares de clúster.

Para borrar un par de SVM en un grupo, bórralo para quitar la relación en ambos lados cuando los grupos sean accesibles.

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

El proxy de la CLI de ONTAP no admite la eliminación de pares de clústeres. Para borrar un clúster par, bórralo por UUID a través de la API de REST.

Primero, enumera los pares para encontrar el 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"

Borra cada UUID devuelto:

 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"

Repite este proceso en ambos grupos de modo ONTAP en el escenario B. Para el escenario A, ejecuta cluster peer delete en el clúster ONTAP externo si es necesario.

Secuencia de comandos de interconexión de clústeres

Guarda el siguiente código como peer_cluster.py. Úsalo en un grupo en modo ONTAP cuando ese grupo deba aceptar una oferta de interconexión de clústeres (PEER_MODE=accept). La interconexión híbrida y el escenario B usan el modo accept. Usa el modo offer (PEER_MODE=offer) solo si cluster peer create -generate-passphrase falla a través de ontap_execute o no devuelve una frase de contraseña. Esta secuencia de comandos requiere Python 3.8 o una versión posterior, y la biblioteca estándar de 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()

Variables de entorno opcionales: EXPIRY_TIME, PEER_APPLICATIONS (snapmirror predeterminado), ENCRYPTION_PROPOSED y IPSPACE (Gcnv predeterminado).

Secuencia de comandos de interconexión de SVM (alternativa)

Usa peer_svm.py solo cuando vserver peer create no esté disponible a través del proxy de la CLI de ONTAP. En su lugar, usa vserver peer create y vserver peer accept como se describe en SVM de pares.

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

¿Qué sigue?