added failover, failover comit and rollback
This commit is contained in:
+138
-5
@@ -14,7 +14,7 @@ import logging
|
||||
import time
|
||||
import json
|
||||
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 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)}")
|
||||
|
||||
|
||||
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})')
|
||||
|
||||
local_site = LocalSite(self.client.zvm_address, self.client.token)
|
||||
@@ -217,9 +217,10 @@ class VPGs:
|
||||
return
|
||||
|
||||
vm_id = vm_dict[vm_name]['VmIdentifier']
|
||||
vm_payload = {
|
||||
"VmIdentifier": vm_id
|
||||
}
|
||||
if vm_payload is None:
|
||||
vm_payload = {
|
||||
"VmIdentifier": vm_id
|
||||
}
|
||||
return self.add_vm_to_vpg(vpg_name, vm_payload)
|
||||
|
||||
def add_vm_to_vpg(self, vpg_name, vm_list_payload):
|
||||
@@ -440,6 +441,84 @@ class VPGs:
|
||||
logging.error(f"Unexpected error: {e}")
|
||||
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):
|
||||
"""
|
||||
Rollback failover for a given VPG by its name.
|
||||
@@ -490,6 +569,60 @@ class VPGs:
|
||||
logging.error(f"Unexpected error: {e}")
|
||||
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):
|
||||
"""
|
||||
Deletes a VPG by its name.
|
||||
|
||||
Reference in New Issue
Block a user