added failover, failover comit and rollback

This commit is contained in:
Kosta Mushkin
2025-10-29 13:24:55 -04:00
parent 61e1bb6a67
commit ad5f6ac4c4
+138 -5
View File
@@ -14,7 +14,7 @@ import logging
import time import time
import json import json
from .tasks import Tasks from .tasks import Tasks
from .common import ZertoVPGStatus, ZertoVPGSubstatus, ZertoProtectedSiteType, ZertoRecoverySiteType, ZertoVPGPriority from .common import ZertoVPGStatus, ZertoVPGSubstatus, ZertoProtectedSiteType, ZertoRecoverySiteType, ZertoVPGPriority, ZertoCommitPolicy, ZertoShutdownPolicy
from .localsite import LocalSite from .localsite import LocalSite
from typing import Optional, Union, Dict, List from typing import Optional, Union, Dict, List
@@ -204,7 +204,7 @@ class VPGs:
raise TimeoutError(f"VPG {vpg_name} did not reach the {ZertoVPGStatus.get_name_by_value(expected_status.value)} state within the allotted time. Current status: {ZertoVPGStatus.get_name_by_value(vpg_status.value)}") raise TimeoutError(f"VPG {vpg_name} did not reach the {ZertoVPGStatus.get_name_by_value(expected_status.value)} state within the allotted time. Current status: {ZertoVPGStatus.get_name_by_value(vpg_status.value)}")
def add_vm_to_vpg_by_name(self, vpg_name, vm_name): def add_vm_to_vpg_by_name(self, vpg_name, vm_name, vm_payload=None):
logging.info(f'VPGs.add_vm_to_vpg_by_name(zvm_address={self.client.zvm_address}, vpg_name={vpg_name}, vm_name={vm_name})') logging.info(f'VPGs.add_vm_to_vpg_by_name(zvm_address={self.client.zvm_address}, vpg_name={vpg_name}, vm_name={vm_name})')
local_site = LocalSite(self.client.zvm_address, self.client.token) local_site = LocalSite(self.client.zvm_address, self.client.token)
@@ -217,9 +217,10 @@ class VPGs:
return return
vm_id = vm_dict[vm_name]['VmIdentifier'] vm_id = vm_dict[vm_name]['VmIdentifier']
vm_payload = { if vm_payload is None:
"VmIdentifier": vm_id vm_payload = {
} "VmIdentifier": vm_id
}
return self.add_vm_to_vpg(vpg_name, vm_payload) return self.add_vm_to_vpg(vpg_name, vm_payload)
def add_vm_to_vpg(self, vpg_name, vm_list_payload): def add_vm_to_vpg(self, vpg_name, vm_list_payload):
@@ -440,6 +441,84 @@ class VPGs:
logging.error(f"Unexpected error: {e}") logging.error(f"Unexpected error: {e}")
raise raise
def failover(self, vpg_name, checkpoint_identifier=None, vm_name_list=None, commit_policy=ZertoCommitPolicy.Commit, time_to_wait_before_shutdown_sec=3600, shutdown_policy=ZertoShutdownPolicy.NONE, is_reverse_protection=True, sync=True):
"""
Initiate a failover for a given VPG by its name.
:param vpg_name: The name of the VPG.
:param checkpoint_identifier: checkpoint_identifier can be received by list_checkpoints, if not provided uses the latest checkpoint.
:param vm_name_list: List of VM names to failover (optional, if not provided fails over all VMs in the VPG).
:param commit_policy: Commit policy for the failover (default: ZertoCommitPolicy.Commit).
:param time_to_wait_before_shutdown_sec: Time to wait before shutdown in seconds (default: 3600).
:param shutdown_policy: Shutdown policy for the failover (default: ZertoShutdownPolicy.NONE).
:param is_reverse_protection: Whether to enable reverse protection (default: False).
:param sync: wait until task is completed.
:return: Response from the Zerto API.
"""
logging.info(f'VPGs.failover(zvm_address={self.client.zvm_address}, vpg_name={vpg_name}, checkpoint_identifier={checkpoint_identifier}, vm_name_list={vm_name_list}, sync={sync})')
# Retrieve the VPG identifier using the VPG name
vpg_info = self.list_vpgs(vpg_name=vpg_name)
vpg_identifier = vpg_info['VpgIdentifier']
logging.debug(f"Found VPG '{vpg_name}' with Identifier: {vpg_identifier}")
url = f"https://{self.client.zvm_address}/v1/vpgs/{vpg_identifier}/Failover"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
payload = {
"commitPolicy": commit_policy.value,
"timeToWaitBeforeShutdownInSec": time_to_wait_before_shutdown_sec,
"shutdownPolicy": shutdown_policy.value,
"isReverseProtection": is_reverse_protection
}
if checkpoint_identifier:
payload['checkpointIdentifier'] = checkpoint_identifier
vm_identifier_list = []
if vm_name_list:
for vm in vm_name_list:
vm_info = self.list_vms(vm_name=vm)
if not vm_info:
logging.error(f'failover vm={vm} not found')
return
vm_identifier_list.append(vm_info[0]['VmIdentifier'])
if vm_identifier_list:
payload['vmIdentifiers'] = vm_identifier_list
try:
logging.info(f"Initiating failover for VPG '{vpg_name}', payload={payload}")
response = requests.post(url, headers=headers, json=payload, verify=self.client.verify_certificate)
response.raise_for_status()
task_id = response.json()
logging.info(f"Failover initiated for VPG {vpg_name}, task_id = {task_id}")
if sync:
# Wait for task completion
self.tasks.wait_for_task_completion(task_id, timeout=30, interval=5)
return response.json()
except requests.exceptions.RequestException as e:
if e.response is not None:
logging.error(f"HTTPError: {e.response.status_code} - {e.response.reason}")
try:
error_details = e.response.json()
logging.error(f"Error Message: {error_details.get('Message', 'No detailed error message available')}")
except ValueError:
logging.error(f"Response content: {e.response.text}")
else:
logging.error("HTTPError occurred with no response attached.")
raise
except Exception as e:
logging.error(f"Unexpected error: {e}")
raise
def rollback_failover(self, vpg_name, sync=True): def rollback_failover(self, vpg_name, sync=True):
""" """
Rollback failover for a given VPG by its name. Rollback failover for a given VPG by its name.
@@ -490,6 +569,60 @@ class VPGs:
logging.error(f"Unexpected error: {e}") logging.error(f"Unexpected error: {e}")
raise raise
def commit_failover(self, vpg_name, is_reverse_protection=True, sync=True):
"""
Commit failover for a given VPG by its name.
:param vpg_name: The name of the VPG.
:param sync: wait until task is completed.
"""
logging.info(f'VPGs.comit_failover(zvm_address={self.client.zvm_address}, vpg_name={vpg_name}, sync={sync})')
# Retrieve the VPG identifier using the VPG name
vpg_info = self.list_vpgs(vpg_name=vpg_name)
vpg_identifier = vpg_info['VpgIdentifier']
logging.info(f"Found VPG '{vpg_name}' with Identifier: {vpg_identifier}")
payload = {
"isReverseProtection": is_reverse_protection
}
url = f"https://{self.client.zvm_address}/v1/vpgs/{vpg_identifier}/FailoverCommit"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
try:
logging.info(f"Rollback failover for VPG '{vpg_name}'...")
response = requests.post(url, headers=headers, json=payload, verify=self.client.verify_certificate)
response.raise_for_status()
task_id = response.json()
logging.info(f"Commit failover for VPG {vpg_name}, task_id = {task_id}")
if sync:
# Wait for task completion
self.tasks.wait_for_task_completion(task_id, timeout=30, interval=5)
return response.json()
except requests.exceptions.RequestException as e:
if e.response is not None:
logging.error(f"HTTPError: {e.response.status_code} - {e.response.reason}")
try:
error_details = e.response.json()
logging.error(f"Error Message: {error_details.get('Message', 'No detailed error message available')}")
except ValueError:
logging.error(f"Response content: {e.response.text}")
else:
logging.error("HTTPError occurred with no response attached.")
raise
except Exception as e:
logging.error(f"Unexpected error: {e}")
raise
def delete_vpg(self, vpg_name, force=False, keep_recovery_volumes=True): def delete_vpg(self, vpg_name, force=False, keep_recovery_volumes=True):
""" """
Deletes a VPG by its name. Deletes a VPG by its name.