Files
zvml-python-sdk/zvml/vms.py
T
Kosta Mushkin ba16ef9a08 initial commit
2025-04-12 14:33:32 -04:00

349 lines
16 KiB
Python

# Legal Disclaimer
# This script is an example script and is not supported under any Zerto support program or service.
# The author and Zerto further disclaim all implied warranties including, without limitation,
# any implied warranties of merchantability or of fitness for a particular purpose.
# In no event shall Zerto, its authors or anyone else involved in the creation,
# production or delivery of the scripts be liable for any damages whatsoever (including,
# without limitation, damages for loss of business profits, business interruption, loss of business
# information, or other pecuniary loss) arising out of the use of or the inability to use the sample
# scripts or documentation, even if the author or Zerto has been advised of the possibility of such damages.
# The entire risk arising out of the use or performance of the sample scripts and documentation remains with you.
import requests
import logging
import json
class VMs:
def __init__(self, client):
self.client = client
def list_vms(self, vm_identifier=None, vpg_name=None, vm_name=None, status=None, sub_status=None,
protected_site_type=None, recovery_site_type=None, protected_site_identifier=None,
recovery_site_identifier=None, organization_name=None, priority=None,
vpg_identifier=None, include_backuped_vms=None, include_mounted_vms=True):
"""
Get information about protected virtual machines. If vm_identifier is provided,
returns details about a specific VM, otherwise returns a filtered list of VMs. (Auth)
Args:
vm_identifier (str, optional): The identifier of a specific VM to get information about
vpg_name (str, optional): The name of the VPG
vm_name (str, optional): The name of the VM
status (str, optional): The status of the VPG
sub_status (str, optional): The sub-status of the VPG
protected_site_type (str, optional): The protected site type
recovery_site_type (str, optional): The recovery site type
protected_site_identifier (str, optional): The identifier of the protected site
recovery_site_identifier (str, optional): The identifier of the recovery site
organization_name (str, optional): The ZORG name
priority (str, optional): The VPG priority
vpg_identifier (str, optional): The identifier of the VPG (used with vm_identifier)
include_backuped_vms (bool, optional): Include VMs in backup targets
include_mounted_vms (bool, optional): Include mounted VMs in the response
Returns:
dict or list: Details of a specific VM if vm_identifier is provided,
otherwise an array of protected VMs matching the filter criteria
Raises:
requests.exceptions.RequestException: If the API request fails
"""
# Build the URL based on whether we're getting a specific VM or listing VMs
base_url = f"https://{self.client.zvm_address}/v1/vms"
url = f"{base_url}/{vm_identifier}" if vm_identifier else base_url
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
# Build params based on whether we're getting a specific VM or listing VMs
if vm_identifier:
params = {
'vpgIdentifier': vpg_identifier,
'includeBackupedVms': include_backuped_vms,
'includeMountedVms': include_mounted_vms
}
log_msg = f"VMs.list_vms: Fetching VM {vm_identifier}"
else:
params = {
'vpgName': vpg_name,
'vmName': vm_name,
'status': status,
'subStatus': sub_status,
'protectedSiteType': protected_site_type,
'recoverySiteType': recovery_site_type,
'protectedSiteIdentifier': protected_site_identifier,
'recoverySiteIdentifier': recovery_site_identifier,
'organizationName': organization_name,
'priority': priority,
'vmIdentifier': vm_identifier,
'includeBackupedVms': include_backuped_vms,
'includeMountedVms': include_mounted_vms
}
log_msg = "VMs.list_vms: Fetching VMs"
# Remove None values from params
params = {k: v for k, v in params.items() if v is not None}
logging.info(f"{log_msg} with params: {params}")
try:
response = requests.get(url, headers=headers, params=params, verify=self.client.verify_certificate)
response.raise_for_status()
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
def restore_vm(self, vm_identifier, vpg_identifier, restored_vm_name, checkpoint_identifier,
journal_vm_restore_settings, commit_policy=0, shutdown_policy=0,
time_to_wait_before_continue_in_seconds=0):
"""
Restore a VM from a specific checkpoint. (Auth)
Args:
vm_identifier (str): The identifier of the VM to restore
vpg_identifier (str): The identifier of the VPG
restored_vm_name (str): The name for the restored VM
checkpoint_identifier (str): The identifier of the checkpoint to restore from
journal_vm_restore_settings (dict): Settings for the restored VM with structure:
{
"datastoreIdentifier": str,
"nics": [
{
"hypervisor": {
"dnsSuffix": str,
"ipConfig": {
"gateway": str,
"isDhcp": bool,
"primaryDns": str,
"secondaryDns": str,
"staticIp": str,
"subnetMask": str
},
"networkIdentifier": str,
"shouldReplaceMacAddress": bool
},
"nicIdentifier": str
}
],
"volumes": [
{
"datastore": {
"datastoreIdentifier": str,
"isThin": bool
},
"volumeIdentifier": str
}
]
}
commit_policy (int, optional): The commit policy. Defaults to 0
shutdown_policy (int, optional): The shutdown policy. Defaults to 0
time_to_wait_before_continue_in_seconds (int, optional): Time to wait before continuing. Defaults to 0
Returns:
dict: Response from the server
Raises:
requests.exceptions.RequestException: If the API request fails
"""
url = f"https://{self.client.zvm_address}/v1/vms/{vm_identifier}/Restore"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
data = {
"vpgIdentifier": vpg_identifier,
"restoredVmName": restored_vm_name,
"checkpointIdentifier": checkpoint_identifier,
"commitPolicy": commit_policy,
"shutdownPolicy": shutdown_policy,
"timeToWaitBeforeContinueInSeconds": time_to_wait_before_continue_in_seconds,
"journalVMRestoreSettings": journal_vm_restore_settings
}
logging.info(f"VMs.restore_vm: Restoring VM {vm_identifier} from checkpoint {checkpoint_identifier}")
logging.info(f"VMs.restore_vm: Data: {json.dumps(data, indent=2)}")
try:
response = requests.post(url, headers=headers, json=data, verify=self.client.verify_certificate)
response.raise_for_status()
return response.json() if response.content else None
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
def restore_vm_commit(self, vm_identifier):
"""
Commit a restored VM. (Auth)
Args:
vm_identifier (str): The identifier of the VM to commit
Returns:
dict: Response from the server if any
Raises:
requests.exceptions.RequestException: If the API request fails
"""
url = f"https://{self.client.zvm_address}/v1/vms/{vm_identifier}/RestoreCommit"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
logging.info(f"VMs.restore_vm_commit: Committing restored VM {vm_identifier}")
try:
response = requests.post(url, headers=headers, verify=self.client.verify_certificate)
response.raise_for_status()
return response.json() if response.content else None
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
def restore_vm_rollback(self, vm_identifier):
"""
Rollback a restored VM. (Auth)
Args:
vm_identifier (str): The identifier of the VM to rollback
Returns:
dict: Response from the server if any
Raises:
requests.exceptions.RequestException: If the API request fails
"""
url = f"https://{self.client.zvm_address}/v1/vms/{vm_identifier}/RestoreRollback"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
logging.info(f"VMs.restore_vm_rollback: Rolling back restored VM {vm_identifier}")
try:
response = requests.post(url, headers=headers, verify=self.client.verify_certificate)
response.raise_for_status()
return response.json() if response.content else None
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
def list_vm_points_in_time(self, vm_identifier, vpg_identifier=None, start_date=None, end_date=None):
"""
Get points in time for a specific VM. (Auth)
Args:
vm_identifier (str): The identifier of the VM
vpg_identifier (str, optional): The identifier of the VPG
start_date (str, optional): The filter interval start date-time
end_date (str, optional): The filter interval end date-time
Returns:
list: Array of points in time for the specified VM
Raises:
requests.exceptions.RequestException: If the API request fails
"""
url = f"https://{self.client.zvm_address}/v1/vms/{vm_identifier}/pointsInTime"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
params = {
'vpgIdentifier': vpg_identifier,
'startDate': start_date,
'endDate': end_date
}
# Remove None values from params
params = {k: v for k, v in params.items() if v is not None}
logging.info(f"VMs.list_vm_points_in_time: Fetching points in time for VM {vm_identifier}")
try:
response = requests.get(url, headers=headers, params=params, verify=self.client.verify_certificate)
response.raise_for_status()
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
def list_vm_points_in_time_stats(self, vm_identifier, vpg_identifier=None):
"""
Get the earliest and latest points in time for the VM. (Auth)
VpgId may be required if the VM is protected by more than one VPG.
Args:
vm_identifier (str): The identifier of the VM
vpg_identifier (str, optional): The identifier of the VPG which protects the VM
Returns:
dict: Statistics about the earliest and latest points in time for the VM
Raises:
requests.exceptions.RequestException: If the API request fails
"""
url = f"https://{self.client.zvm_address}/v1/vms/{vm_identifier}/pointsInTime/stats"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
params = {'vpgIdentifier': vpg_identifier} if vpg_identifier else {}
logging.info(f"VMs.list_vm_points_in_time_stats: Fetching points in time stats for VM {vm_identifier}")
try:
response = requests.get(url, headers=headers, params=params, verify=self.client.verify_certificate)
response.raise_for_status()
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