initial commit

This commit is contained in:
Kosta Mushkin
2025-04-12 14:33:32 -04:00
parent 955ec79014
commit ba16ef9a08
83 changed files with 10290 additions and 2 deletions
+276 -2
View File
@@ -1,2 +1,276 @@
# zvml-python-sdk
zerto virtual manager linux python library with examples
# zerto-python-library
A Python library for interacting with the Zerto Virtual Manager (ZVM) API.
## Overview
This library provides a comprehensive Python interface to manage and automate Zerto Virtual Replication operations. It includes functionality for:
- Managing Virtual Protection Groups (VPGs)
- Handling VM protection and recovery
- Managing checkpoints and recovery points
- Monitoring alerts and events
- Managing licenses
- Configuring service profiles
- Handling encryption detection
- Managing datastores and VRAs
- Working with server date/time settings
- Managing ZORGs (Zerto Organizations)
## Installation
git clone https://github.com/your-repo/zerto-python-library.git
cd zerto-python-library
pip install -r requirements.txt
## Dependencies
- requests
- urllib3
- logging
- json
- typing
## Library Structure
The library is organized into several modules:
- `zvml/` - Core library components
- `alerts.py` - Alert management and monitoring
- `checkpoints.py` - Checkpoint operations and management
- `common.py` - Common enums and utilities
- `encryptiondetection.py` - Encryption detection functionality
- `license.py` - License management
- `localsite.py` - Local site operations
- `recovery_reports.py` - Recovery reporting functionality
- `server_date_time.py` - Server time operations
- `service_profiles.py` - Service profile configuration
- `tasks.py` - Task management and monitoring
- `virtualization_sites.py` - Site management operations
- `vpgs.py` - VPG operations and management
- `vras.py` - VRA deployment and management
- `zorgs.py` - ZORG operations
## Requirements
- Python 3.6+
- Zerto Virtual Replication environment
- Network access to ZVM server
- Keycloak authentication credentials
## Getting Started
1. Clone the repository
2. Install required dependencies
3. Configure your Zerto environment credentials
4. Run the example scripts to understand basic operations
5. Integrate the library into your automation workflows
## Authentication
The library uses Keycloak authentication. You'll need:
- ZVM server address
- Client ID
- Client Secret (* KeyCloak)
- Optional: SSL verification settings
## KeyCloak
In order to use api client you'll need:
1. Login into ZVML keycloak UI. https:////<ZVML IP>/auth
2. Select zerto realm (Switch from Master to Zerto using left menu drop box)
3. Click on "Clients" using the left menu
4. Click on CreateClient button
5. Fill in client id (will be used for authentication) and client name (logical), click next
6. Enable the following options: "Client authentication", "Authorization", "Standard flow", "Direct access grants",
"Implicit flow", "OAuth 2.0 Device Authorization Grant", click next, click save.
7. Select "Service account roles" tab, click on "Assign Role" button, check mark "admin" (or another role), click on "Assign" button
8. Select "Credentials" tab, copy "Client Secret"
9. Use the combination of the created client id and the client secret for authntication in your code
## Error Handling
The library includes comprehensive error handling and logging:
- Input validation
- Error status checking
- Detailed error messages
- Operation status logging
## Examples
Each example script demonstrates specific functionality:
### Alert Management
`alerts_example.py` - Simple alert monitoring and management (list, dismiss, undismiss):
```bash
python examples/alerts_example.py \
--zvm_address "192.168.111.20" \
--client_id "zerto-api" \
--client_secret "your-secret-here" \
--ignore_ssl
```
### VPG Management with VMs
`vpg_vms_example.py` - VPG creation and VM management between VPGs:
```bash
python examples/vpg_vms_example.py \
--zvm_address "192.168.111.20" \
--client_id "zerto-api" \
--client_secret "your-secret-here" \
--ignore_ssl \
--vm1 "vm-name-1" \
--vm2 "vm-name-2"
```
### VPG Failover Testing
`vpg_failover_example.py` - Complete VPG lifecycle including failover testing:
```bash
python examples/vpg_failover_example.py \
--zvm_address "192.168.111.20" \
--client_id "zerto-api" \
--client_secret "your-secret-here" \
--ignore_ssl
```
### VRA Management
`vras_example.py` - Interactive VRA deployment and management:
```bash
python examples/vras_example.py \
--zvm_address "192.168.111.20" \
--client_id "zerto-api" \
--client_secret "your-secret-here" \
--ignore_ssl
```
### ZORG Management
`zorgs_example.py` - ZORG information retrieval and management:
```bash
python examples/zorgs_example.py \
--zvm_address "192.168.111.20" \
--client_id "zerto-api" \
--client_secret "your-secret-here" \
--ignore_ssl
```
### License Management
`license_example.py` - License information and management:
```bash
python examples/license_example.py \
--zvm_address "192.168.111.20" \
--client_id "zerto-api" \
--client_secret "your-secret-here" \
--ignore_ssl
```
### Events Monitoring
`events_example.py` - Monitor and retrieve Zerto events:
```bash
python examples/events_example.py \
--zvm_address "192.168.111.20" \
--client_id "zerto-api" \
--client_secret "your-secret-here" \
--ignore_ssl
```
### Encryption Detection (not operationa yet)
`encryption_detection_example.py` - Manage encryption detection settings:
```bash
python examples/encryption_detection_example.py \
--zvm_address "192.168.111.20" \
--client_id "zerto-api" \
--client_secret "your-secret-here" \
--ignore_ssl
```
### Service Profiles
`service_profiles_example.py` - Manage service profiles:
```bash
python examples/service_profiles_example.py \
--zvm_address "192.168.111.20" \
--client_id "zerto-api" \
--client_secret "your-secret-here" \
--ignore_ssl
```
### Volumes Management
`volumes_example.py` - Manage protected volumes:
```bash
python examples/volumes_example.py \
--zvm_address "192.168.111.20" \
--client_id "zerto-api" \
--client_secret "your-secret-here" \
--ignore_ssl
```
### Server Date and Time example
`server_date_time_example.py` - Manage protected volumes:
```bash
python examples/server_date_time_example.py \
--zvm_address "192.168.111.20" \
--client_id "zerto-api" \
--client_secret "your-secret-here" \
--ignore_ssl
```
### Datastore management example
`datastore_example.py` - Manage protected volumes:
```bash
python examples/datastore_example.py \
--zvm_address "192.168.111.20" \
--client_id "zerto-api" \
--client_secret "your-secret-here" \
--ignore_ssl
```
### Localsite management
`localsite_example.py` - Manage protected volumes:
```bash
python examples/localsite_example.py \
--zvm_address "192.168.111.20" \
--client_id "zerto-api" \
--client_secret "your-secret-here" \
--ignore_ssl
```
```bash
python examples/peersites_example.py \
--site1_zvm_address <zvm1_address> \
--site1_client_id <client_id1> \
--site1_client_secret <secret1> \
--site2_zvm_address <zvm2_address> \
--site2_client_id <client_id2> \
--site2_client_secret <secret2> \
--ignore_ssl
```
```bash
python examples/tweaks_example.py \
--zvm_address <zvm_address> \
--client_id <client_id> \
--client_secret <client_secret> \
--ignore_ssl
```
Each example includes detailed comments explaining the functionality and demonstrates proper error handling and best practices for using the ZVML SDK.
## Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
## License
This project includes a legal disclaimer. See the header of each file for details.
For detailed API documentation and examples, please refer to the individual module files and example scripts.
View File
+106
View File
@@ -0,0 +1,106 @@
# 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.
"""
Zerto Alert Management Example Script
This script demonstrates basic alert management using the Zerto Virtual Manager (ZVM) API.
It shows how to list alerts and perform basic alert operations (dismiss/undismiss).
Key Features:
1. Alert Monitoring:
- List all current alerts
- Display alert details
- Manage alert states (dismiss/undismiss)
Required Arguments:
--zvm_address: ZVM server address
--client_id: Keycloak client ID
--client_secret: Keycloak client secret
--ignore_ssl: Ignore SSL certificate verification (optional)
Example Usage:
python examples/alerts_example.py \
--zvm_address "192.168.111.20" \
--client_id "zerto-api" \
--client_secret "your-secret-here" \
--ignore_ssl
"""
import sys
import os
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import argparse
import logging
import urllib3
import json
from zvml import ZVMLClient
# Disable SSL warnings
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
def main():
parser = argparse.ArgumentParser(description="Zerto Alerts Example")
parser.add_argument("--zvm_address", required=True, help="ZVM address")
parser.add_argument('--client_id', required=True, help='Keycloak client ID')
parser.add_argument('--client_secret', required=True, help='Keycloak client secret')
parser.add_argument("--ignore_ssl", action="store_true", help="Ignore SSL certificate verification")
args = parser.parse_args()
logging.basicConfig(level=logging.INFO)
try:
# Connect to ZVM
client = ZVMLClient(
zvm_address=args.zvm_address,
client_id=args.client_id,
client_secret=args.client_secret,
verify_certificate=not args.ignore_ssl
)
# Get current alerts
alerts = client.alerts.get_alerts()
if not alerts:
logging.info("No alerts found in the system")
return
# Display alerts
logging.info(f"Found {len(alerts)} alerts:")
for alert in alerts:
logging.info(f"\nAlert Details:")
logging.info(f" Description: {alert.get('Description')}")
logging.info(f" Status: {alert.get('Status')}")
logging.info(f" Level: {alert.get('Level')}")
logging.info(f" Turn off Time: {alert.get('TurnedOffTime')}")
logging.info(f" Entity: {alert.get('Entity')}")
logging.info(f" Help Identifier: {alert.get('HelpIdentifier')}")
# Get alert identifier
alert_id = alert.get('Link', {}).get('identifier')
if alert_id:
# Dismiss alert
input(f"\nPress Enter to dismiss alert {alert_id}...")
client.alerts.dismiss_alert(alert_identifier=alert_id)
logging.info(f"Alert {alert_id} dismissed")
# Undismiss alert
input(f"Press Enter to undismiss alert {alert_id}...")
client.alerts.undismiss_alert(alert_identifier=alert_id)
logging.info(f"Alert {alert_id} undismissed")
except Exception as e:
logging.exception("Error:")
logging.error(f"Error: {e}")
if __name__ == "__main__":
main()
+100
View File
@@ -0,0 +1,100 @@
# 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.
"""
Zerto Datastores Example Script
This script demonstrates how to retrieve and list datastores from a Zerto environment.
The script performs the following steps:
1. Connects to Zerto Virtual Manager (ZVM)
2. Gets the local site identifier
3. Lists all datastores in the site
4. Gets detailed information about a specific datastore
Required Arguments:
--zvm_address: ZVM address
--client_id: Keycloak client ID
--client_secret: Keycloak client secret
Optional Arguments:
--ignore_ssl: Ignore SSL certificate verification
Example Usage:
python examples/datastore_example.py \
--zvm_address <zvm_address> \
--client_id <client_id> \
--client_secret <client_secret> \
--ignore_ssl
"""
import sys
import os
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import argparse
import logging
import urllib3
import json
from zvml import ZVMLClient
# Disable SSL warnings
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
def main():
parser = argparse.ArgumentParser(description="Zerto Datastores Example")
parser.add_argument("--zvm_address", required=True, help="ZVM address")
parser.add_argument('--client_id', required=True, help='Keycloak client ID')
parser.add_argument('--client_secret', required=True, help='Keycloak client secret')
parser.add_argument("--ignore_ssl", action="store_true", help="Ignore SSL certificate verification")
args = parser.parse_args()
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
try:
# Connect to ZVM
logging.info(f"Connecting to ZVM at {args.zvm_address}")
client = ZVMLClient(
zvm_address=args.zvm_address,
client_id=args.client_id,
client_secret=args.client_secret,
verify_certificate=not args.ignore_ssl
)
# Get local site identifier
local_site = client.localsite.get_local_site()
site_identifier = local_site.get('SiteIdentifier')
logging.info(f"Local site identifier: {site_identifier}")
# Get datastores using VirtualizationSites API
datastores = client.virtualization_sites.get_virtualization_site_datastores(site_identifier)
logging.info("\nDatastores in site:")
logging.info(json.dumps(datastores, indent=2))
# Get specific datastore details if any exist
if datastores:
first_ds_id = datastores[0].get('DatastoreIdentifier')
logging.info(f"\nGetting details for specific datastore: {first_ds_id}")
ds_details = client.datastores.list_datastores(first_ds_id)
logging.info("Datastore details:")
logging.info(json.dumps(ds_details, indent=2))
except Exception as e:
logging.error(f"Error occurred: {e}")
raise
if __name__ == "__main__":
main()
+117
View File
@@ -0,0 +1,117 @@
#!/usr/bin/env python3
# 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.
import argparse
import sys
import os
import time
import paramiko
import logging
import urllib3
from zvml.client import Client
from zvml.encryptiondetection import EncryptionDetection
# Disable SSL warnings
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
def setup_client(args):
"""Initialize and return Zerto client"""
client = Client(
zvm_address=args.zvm_address,
client_id=args.client_id,
client_secret=args.client_secret,
verify_certificate=not args.ignore_ssl
)
return client
def setup_encrypted_volume(ssh_host: str, ssh_user: str, ssh_password: str):
"""Create and encrypt a volume on the Linux VM."""
try:
# Connect to the Linux VM
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(ssh_host, username=ssh_user, password=ssh_password)
logging.info(f"Successfully connected to {ssh_host}")
# Create a test file system
commands = [
"sudo dd if=/dev/zero of=/root/container.img bs=1M count=100", # Create 100MB file
"sudo losetup /dev/loop0 /root/container.img", # Set up loop device
"sudo cryptsetup -y luksFormat /dev/loop0", # Encrypt with LUKS
"echo 'YES' | sudo cryptsetup luksOpen /dev/loop0 encrypted_volume", # Open encrypted volume
"sudo mkfs.ext4 /dev/mapper/encrypted_volume", # Create filesystem
"sudo mkdir -p /mnt/encrypted", # Create mount point
"sudo mount /dev/mapper/encrypted_volume /mnt/encrypted", # Mount encrypted volume
"sudo dd if=/dev/urandom of=/mnt/encrypted/testfile bs=1M count=50" # Create test file with random data
]
for cmd in commands:
logging.info(f"Executing: {cmd}")
stdin, stdout, stderr = ssh.exec_command(cmd)
exit_status = stdout.channel.recv_exit_status()
if exit_status != 0:
error = stderr.read().decode()
logging.error(f"Command failed with status {exit_status}: {error}")
raise Exception(f"Command failed: {cmd}")
logging.info("Successfully created and encrypted test volume")
except Exception as e:
logging.error(f"Failed to setup encrypted volume: {str(e)}")
raise
finally:
ssh.close()
def main():
parser = argparse.ArgumentParser(description="Encryption Detection Example")
parser.add_argument("--zvm_address", required=True, help="ZVM address")
parser.add_argument('--client_id', required=True, help='Keycloak client ID')
parser.add_argument('--client_secret', required=True, help='Keycloak client secret')
parser.add_argument("--ignore_ssl", action="store_true", help="Ignore SSL certificate verification")
parser.add_argument("--vm_address", required=True, help="Linux VM address")
parser.add_argument("--vm_user", required=True, help="Linux VM username")
parser.add_argument("--vm_password", required=True, help="Linux VM password")
args = parser.parse_args()
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
try:
# Setup client
client = setup_client(args)
encryption_detection = EncryptionDetection(client)
logging.info("Successfully connected to ZVM")
# Setup encrypted volume on Linux VM
setup_encrypted_volume(args.vm_address, args.vm_user, args.vm_password)
# Wait for encryption detection to process
logging.info("Waiting for encryption detection to process (30 seconds)...")
time.sleep(30)
# Check for suspected encrypted volumes
detections = encryption_detection.list_suspected_volumes()
if detections:
logging.info("Suspected encrypted volumes detected:")
for detection in detections:
logging.info(f"Volume: {detection.get('VolumeName', 'Unknown')}")
logging.info(f"Detection Type: {detection.get('DetectionType', 'Unknown')}")
logging.info(f"Confidence Level: {detection.get('ConfidenceLevel', 'Unknown')}")
logging.info("---")
else:
logging.info("No suspected encrypted volumes detected")
except Exception as e:
logging.exception("Error occurred:")
sys.exit(1)
if __name__ == "__main__":
main()
+136
View File
@@ -0,0 +1,136 @@
# 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.
"""
Zerto Events Example Script
This script demonstrates how to retrieve and manage events in a Zerto environment.
The script performs the following steps:
1. Connects to Zerto Virtual Manager (ZVM)
2. Lists available event types
3. Lists available event entities
4. Lists available event categories
5. Retrieves events from the last hour
6. Demonstrates filtered event queries
7. Gets detailed information about specific events
Required Arguments:
--zvm_address: ZVM address
--client_id: Keycloak client ID
--client_secret: Keycloak client secret
Optional Arguments:
--ignore_ssl: Ignore SSL certificate verification
Example Usage:
python examples/events_example.py \
--zvm_address <zvm_address> \
--client_id <client_id> \
--client_secret <client_secret> \
--ignore_ssl
"""
import sys
import os
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import argparse
import logging
import urllib3
import json
from zvml import ZVMLClient
from datetime import datetime, timedelta
# Disable SSL warnings
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
def main():
parser = argparse.ArgumentParser(description="Zerto Events Example")
parser.add_argument("--zvm_address", required=True, help="ZVM address")
parser.add_argument('--client_id', required=True, help='Keycloak client ID')
parser.add_argument('--client_secret', required=True, help='Keycloak client secret')
parser.add_argument("--ignore_ssl", action="store_true", help="Ignore SSL certificate verification")
args = parser.parse_args()
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
try:
# Connect to ZVM
logging.info(f"Connecting to ZVM at {args.zvm_address}")
client = ZVMLClient(
zvm_address=args.zvm_address,
client_id=args.client_id,
client_secret=args.client_secret,
verify_certificate=not args.ignore_ssl
)
# Get event types
logging.info("\nFetching event types...")
event_types = client.events.list_event_types()
logging.info(f"Found {len(event_types)} event types:")
logging.info(json.dumps(event_types[:3], indent=2)) # Show first 3 for brevity
# Get event entities
logging.info("\nFetching event entities...")
event_entities = client.events.list_event_entities()
logging.info(f"Found {len(event_entities)} event entities:")
logging.info(json.dumps(event_entities[:3], indent=2)) # Show first 3 for brevity
# Get event categories
logging.info("\nFetching event categories...")
event_categories = client.events.list_event_categories()
logging.info(f"Found {len(event_categories)} event categories:")
logging.info(json.dumps(event_categories[:3], indent=2)) # Show first 3 for brevity
# Get events from the last 1 hour
start_date = (datetime.utcnow() - timedelta(hours=1)).isoformat() + 'Z'
end_date = datetime.utcnow().isoformat() + 'Z'
logging.info(f"\nFetching events from {start_date} to {end_date}...")
events = client.events.list_events(
start_date=start_date,
end_date=end_date
)
logging.info(f"Found {len(events)} events in the last 24 hours:")
if events:
logging.info(json.dumps(events[:3], indent=2)) # Show first 3 for brevity
# Get events with filters
logging.info("\nFetching filtered events...")
filtered_events = client.events.list_events(
start_date=start_date,
end_date=end_date,
event_type=18, # Using numeric event type
category="Events" # Using correct category from list_event_categories
)
logging.info(f"Found {len(filtered_events)} filtered events:")
if filtered_events:
logging.info(json.dumps(filtered_events[:3], indent=2)) # Show first 3 for brevity
# If we have any events, get details for a specific event
if events:
event_id = events[0].get('EventIdentifier')
logging.info(f"\nFetching details for event {event_id}...")
event_details = client.events.list_events(event_identifier=event_id)
logging.info("Event details:")
logging.info(json.dumps(event_details, indent=2))
except Exception as e:
logging.error(f"Error occurred: {e}")
raise
if __name__ == "__main__":
main()
+127
View File
@@ -0,0 +1,127 @@
# 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.
"""
Zerto License Management Example Script
This script demonstrates how to manage Zerto licenses through the API.
The script performs the following steps:
1. Connects to Zerto Virtual Manager (ZVM)
2. Retrieves current license information
3. Updates the license if a new key is provided
4. Verifies the updated license details
5. Optionally can delete the license (commented out for safety)
Required Arguments:
--zvm_address: ZVM address
--client_id: Keycloak client ID
--client_secret: Keycloak client secret
Optional Arguments:
--license_key: License key to add/update
--ignore_ssl: Ignore SSL certificate verification
Example Usage:
python examples/license_example.py \
--zvm_address <zvm_address> \
--client_id <client_id> \
--client_secret <client_secret> \
--license_key <license_key> \
--ignore_ssl
"""
import sys
import os
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import argparse
import logging
import urllib3
import json
from zvml import ZVMLClient
# Disable SSL warnings
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
def main():
parser = argparse.ArgumentParser(description="Zerto License Example")
parser.add_argument("--zvm_address", required=True, help="ZVM address")
parser.add_argument('--client_id', required=True, help='Keycloak client ID')
parser.add_argument('--client_secret', required=True, help='Keycloak client secret')
parser.add_argument("--license_key", help="License key to add/update")
parser.add_argument("--ignore_ssl", action="store_true", help="Ignore SSL certificate verification")
args = parser.parse_args()
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
try:
# Connect to ZVM
logging.info(f"Connecting to ZVM at {args.zvm_address}")
client = ZVMLClient(
zvm_address=args.zvm_address,
client_id=args.client_id,
client_secret=args.client_secret,
verify_certificate=not args.ignore_ssl
)
# Get current license information
logging.info("\nFetching current license information...")
license_info = client.license.get_license()
if license_info:
logging.info("Current license details:")
logging.info(json.dumps(license_info, indent=2))
else:
logging.info("No license currently installed")
# If license key is provided, update the license
if args.license_key:
logging.info(f"\nUpdating license with new key...")
update_result = client.license.put_license(args.license_key)
if update_result:
logging.info("License update result:")
logging.info(json.dumps(update_result, indent=2))
else:
logging.info("License updated successfully (no content returned)")
# Get updated license information
logging.info("\nFetching updated license information...")
updated_license = client.license.get_license()
if updated_license:
logging.info("Updated license details:")
logging.info(json.dumps(updated_license, indent=2))
# Delete license (commented out for safety - uncomment if needed)
"""
logging.info("\nDeleting license...")
delete_result = client.license.delete_license()
if delete_result:
logging.info("License deletion result:")
logging.info(json.dumps(delete_result, indent=2))
else:
logging.info("License deleted successfully (no content returned)")
# Verify license is deleted
final_check = client.license.get_license()
if not final_check:
logging.info("License successfully removed")
"""
except Exception as e:
logging.error(f"Error occurred: {e}")
raise
if __name__ == "__main__":
main()
+138
View File
@@ -0,0 +1,138 @@
# 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.
"""
Zerto Local Site Management Example Script
This script demonstrates how to manage and retrieve information about the local Zerto site.
The script performs the following steps:
1. Connects to Zerto Virtual Manager (ZVM)
2. Retrieves local site information
3. Gets site pairing statuses
4. Sends usage data to Zerto
5. Manages login banner settings:
- Gets current banner configuration
- Sets a new test banner
- Verifies the updated settings
- Disables the banner
Required Arguments:
--zvm_address: ZVM address
--client_id: Keycloak client ID
--client_secret: Keycloak client secret
Optional Arguments:
--ignore_ssl: Ignore SSL certificate verification
Example Usage:
python examples/localsite_example.py \
--zvm_address <zvm_address> \
--client_id <client_id> \
--client_secret <client_secret> \
--ignore_ssl
"""
import sys
import os
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import argparse
import logging
import urllib3
import json
from zvml import ZVMLClient
# Disable SSL warnings
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
def main():
parser = argparse.ArgumentParser(description="Zerto Local Site Example")
parser.add_argument("--zvm_address", required=True, help="ZVM address")
parser.add_argument('--client_id', required=True, help='Keycloak client ID')
parser.add_argument('--client_secret', required=True, help='Keycloak client secret')
parser.add_argument("--ignore_ssl", action="store_true", help="Ignore SSL certificate verification")
args = parser.parse_args()
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
try:
# Connect to ZVM
logging.info(f"Connecting to ZVM at {args.zvm_address}")
client = ZVMLClient(
zvm_address=args.zvm_address,
client_id=args.client_id,
client_secret=args.client_secret,
verify_certificate=not args.ignore_ssl
)
# Get local site information
logging.info("\nFetching local site information...")
local_site = client.localsite.get_local_site()
logging.info("Local site details:")
logging.info(json.dumps(local_site, indent=2))
# Get pairing statuses
logging.info("\nFetching pairing statuses...")
pairing_statuses = client.localsite.get_pairing_statuses()
logging.info("Pairing statuses:")
logging.info(json.dumps(pairing_statuses, indent=2))
# Send usage data
logging.info("\nSending usage data...")
usage_result = client.localsite.send_usage()
if usage_result:
logging.info("Usage data result:")
logging.info(json.dumps(usage_result, indent=2))
else:
logging.info("Usage data sent successfully (no content returned)")
# Get current login banner settings
logging.info("\nFetching current login banner settings...")
current_banner = client.localsite.get_login_banner()
logging.info("Current login banner settings:")
logging.info(json.dumps(current_banner, indent=2))
# Set new login banner
test_banner = "This is a test login banner.\nAccess restricted to authorized users only."
logging.info("\nSetting new login banner...")
banner_result = client.localsite.set_login_banner(
is_enabled=True,
banner_text=test_banner
)
logging.info("Login banner update result:")
logging.info(banner_result)
# Verify the new banner settings
logging.info("\nVerifying updated login banner settings...")
updated_banner = client.localsite.get_login_banner()
logging.info("Updated login banner settings:")
logging.info(json.dumps(updated_banner, indent=2))
# Disable the login banner
logging.info("\nDisabling login banner...")
disable_result = client.localsite.set_login_banner(
is_enabled=False,
banner_text=""
)
logging.info("Login banner disable result:")
logging.info(disable_result)
except Exception as e:
logging.error(f"Error occurred: {e}")
raise
if __name__ == "__main__":
main()
+147
View File
@@ -0,0 +1,147 @@
# 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.
"""
Zerto Peer Sites Management Example Script
This script demonstrates how to manage peer site relationships between Zerto Virtual Managers (ZVMs).
The script performs the following steps:
1. Connects to two Zerto Virtual Managers (ZVMs)
2. Lists existing peer sites from site 1
3. Checks for and removes any existing pairing with site 2
4. Generates a pairing token at site 2
5. Pairs site 1 with site 2 using the token
6. Verifies the pairing by checking the updated peer sites list
Required Arguments:
--site1_zvm_address: Site 1 ZVM address
--site1_client_id: Site 1 Keycloak client ID
--site1_client_secret: Site 1 Keycloak client secret
--site2_zvm_address: Site 2 ZVM address
--site2_client_id: Site 2 Keycloak client ID
--site2_client_secret: Site 2 Keycloak client secret
Optional Arguments:
--ignore_ssl: Ignore SSL certificate verification
Example Usage:
python examples/peersites_example.py \
--site1_zvm_address <zvm1_address> \
--site1_client_id <client_id1> \
--site1_client_secret <secret1> \
--site2_zvm_address <zvm2_address> \
--site2_client_id <client_id2> \
--site2_client_secret <secret2> \
--ignore_ssl
"""
import sys
import os
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import argparse
import logging
import urllib3
import json
import time
from zvml import ZVMLClient
# Disable SSL warnings
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
def main():
parser = argparse.ArgumentParser(description="Zerto Peer Sites Example")
parser.add_argument("--site1_zvm_address", required=True, help="site 1 ZVM address")
parser.add_argument('--site1_client_id', required=True, help='site 1 Keycloak client ID')
parser.add_argument('--site1_client_secret', required=True, help='site 1 Keycloak client secret')
parser.add_argument("--site2_zvm_address", required=True, help="site 2 ZVM address")
parser.add_argument('--site2_client_id', required=True, help='site 2 Keycloak client ID')
parser.add_argument('--site2_client_secret', required=True, help='site 2 Keycloak client secret')
parser.add_argument("--ignore_ssl", action="store_true", help="Ignore SSL certificate verification")
args = parser.parse_args()
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
try:
# Initialize the site 1 client
site1_client = ZVMLClient(
zvm_address=args.site1_zvm_address,
client_id=args.site1_client_id,
client_secret=args.site1_client_secret,
verify_certificate=not args.ignore_ssl
)
# Initialize the site 2 client
site2_client = ZVMLClient(
zvm_address=args.site2_zvm_address,
client_id=args.site2_client_id,
client_secret=args.site2_client_secret,
verify_certificate=not args.ignore_ssl
)
# Example 1: Get all peer sites from site 1
logging.info("\nExample 1: Getting all peer sites from site 1")
peer_sites = site1_client.peersites.get_peer_sites()
logging.info(f"Found {len(peer_sites)} peer sites:")
logging.info(json.dumps(peer_sites, indent=2))
# Check if site2 is already paired and delete if necessary
site2_already_paired = False
site2_identifier = None
for site in peer_sites:
if site['HostName'] == args.site2_zvm_address:
site2_already_paired = True
site2_identifier = site['SiteIdentifier']
break
if site2_already_paired:
logging.info(f"\nFound existing pairing with {args.site2_zvm_address}, deleting...")
site1_client.peersites.delete_peer_site(site2_identifier)
logging.info("Existing pairing deleted")
# Wait for deletion to complete
time.sleep(5)
# Example 2: Generate pairing token at site 2
logging.info("\nExample 2: Generating pairing token at site 2")
token = site2_client.peersites.generate_token()
logging.info("Generated token:")
logging.info(json.dumps(token, indent=2))
# Example 3: Pair site 1 with site 2
logging.info(f"\nExample 3: Pairing site 1 with site 2 ({args.site2_zvm_address})")
pair_result = site1_client.peersites.pair_site(
hostname=args.site2_zvm_address,
token=token['Token'],
port=9071
)
logging.info("Pairing result:")
logging.info(json.dumps(pair_result, indent=2))
# Wait for pairing to complete
time.sleep(5)
# Example 4: Verify pairing by getting updated peer sites list
logging.info("\nExample 4: Verifying pairing by getting updated peer sites list")
updated_peer_sites = site1_client.peersites.get_peer_sites()
logging.info(f"Found {len(updated_peer_sites)} peer sites:")
logging.info(json.dumps(updated_peer_sites, indent=2))
except Exception as e:
logging.error(f"Error occurred: {e}")
raise
if __name__ == "__main__":
main()
+78
View File
@@ -0,0 +1,78 @@
#!/usr/bin/env python3
import os
import argparse
import logging
import shutil
def create_dataset(base_dir: str, number_of_files: int):
"""Create test files filled with specific content."""
try:
# Validate input
if number_of_files <= 0:
raise ValueError("Number of files must be positive")
# Create directory if it doesn't exist
os.makedirs(base_dir, exist_ok=True)
# Calculate required disk space (approximate)
required_space = number_of_files * 1024 * 1024 # 1MB per file
free_space = shutil.disk_usage(base_dir).free
if free_space < required_space:
raise ValueError(
f"Not enough disk space. Need {required_space / (1024**3):.2f} GB, "
f"but only {free_space / (1024**3):.2f} GB available"
)
# Calculate how many lines we need for ~1MB file
# Each line is about 6 bytes (5 chars + newline)
# 1MB = 1048576 bytes
# Actual calculation: 1048576 / 6 = 174762.67
lines_per_file = 174763
# Create files
for i in range(number_of_files):
file_path = os.path.join(base_dir, f"file{i:04d}.txt")
with open(file_path, 'w') as f:
for _ in range(lines_per_file):
f.write(f'file{i:04d}\n')
# Log every 100 files
if (i + 1) % 100 == 0:
logging.info(f"Created {i + 1} files...")
logging.info(f"Created dataset in {base_dir}")
logging.info(f"Total files created: {number_of_files}")
# Log total size of the dataset
total_size = sum(os.path.getsize(os.path.join(base_dir, f))
for f in os.listdir(base_dir))
logging.info(f"Total dataset size: {total_size / (1024*1024):.2f} MB")
logging.info(f"Average file size: {total_size / (number_of_files * 1024*1024):.2f} MB")
except Exception as e:
logging.error(f"Failed to create dataset: {str(e)}")
raise
def main():
parser = argparse.ArgumentParser(description="Create test dataset")
parser.add_argument("--base_dir", default="~/encryption_test",
help="Base directory for test files (default: ~/encryption_test)")
parser.add_argument("--number_of_files", type=int, required=True,
help="Number of files to create")
args = parser.parse_args()
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
# Expand user path (~/...)
base_dir = os.path.expanduser(args.base_dir)
create_dataset(base_dir, args.number_of_files)
if __name__ == "__main__":
main()
+129
View File
@@ -0,0 +1,129 @@
#!/usr/bin/env python3
import os
import argparse
import logging
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import padding
from cryptography.hazmat.backends import default_backend
import base64
import sys
def generate_key(password: str) -> bytes:
"""Generate an AES key from a password."""
# Using a fixed key for testing (similar to PowerShell script)
key = "Q5KyUru6wn82hlY9k8xUjJOPIC9da41jgRkpt21jo2L="
return base64.b64decode(key)
def decrypt_file(file_path: str, key: bytes) -> bool:
"""Decrypt a single file using AES."""
try:
# Read the encrypted file
with open(file_path, 'rb') as file:
# Read IV (first 16 bytes) and encrypted data
iv = file.read(16)
encrypted_data = file.read()
# Create AES cipher
cipher = Cipher(
algorithms.AES(key),
modes.CBC(iv),
backend=default_backend()
)
decryptor = cipher.decryptor()
# Decrypt the data
padded_data = decryptor.update(encrypted_data) + decryptor.finalize()
# Remove padding
unpadder = padding.PKCS7(128).unpadder()
decrypted_data = unpadder.update(padded_data) + unpadder.finalize()
# Write the decrypted data to a new file (remove .encrypted suffix)
decrypted_path = file_path.rsplit('.encrypted', 1)[0]
with open(decrypted_path, 'wb') as file:
file.write(decrypted_data)
# Remove the encrypted file
os.remove(file_path)
return True
except Exception as e:
logging.error(f"Failed to decrypt {file_path}: {str(e)}")
return False
def decrypt_directory(base_dir: str, password: str):
"""Decrypt all encrypted files in the specified directory."""
try:
# Generate decryption key
key = generate_key(password)
# Get list of encrypted files
files = []
for root, _, filenames in os.walk(base_dir):
for filename in filenames:
if filename.endswith('.encrypted'):
files.append(os.path.join(root, filename))
total_files = len(files)
if total_files == 0:
logging.info("No encrypted files found")
return
logging.info(f"Found {total_files} encrypted files")
# Track progress
successful = 0
failed = 0
# Process each file
for i, file_path in enumerate(files, 1):
logging.info(f"Decrypting {file_path}")
if decrypt_file(file_path, key):
successful += 1
else:
failed += 1
# Log progress every 100 files or at the end
if i % 100 == 0 or i == total_files:
logging.info(f"Processed {i}/{total_files} files...")
# Log final results
logging.info("Decryption complete!")
logging.info(f"Successfully decrypted: {successful} files")
if failed > 0:
logging.warning(f"Failed to decrypt: {failed} files")
except Exception as e:
logging.error(f"Decryption failed: {str(e)}")
raise
def main():
parser = argparse.ArgumentParser(description="Decrypt files in directory")
parser.add_argument("--base_dir", default="~/encryption_test",
help="Base directory containing files to decrypt (default: ~/encryption_test)")
parser.add_argument("--password", required=True,
help="Password for decryption (must match encryption password)")
args = parser.parse_args()
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
# Expand user path (~/...)
base_dir = os.path.expanduser(args.base_dir)
# Verify directory exists
if not os.path.isdir(base_dir):
logging.error(f"Directory not found: {base_dir}")
sys.exit(1)
decrypt_directory(base_dir, args.password)
if __name__ == "__main__":
main()
+140
View File
@@ -0,0 +1,140 @@
#!/usr/bin/env python3
import os
import argparse
import logging
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import padding
from cryptography.hazmat.backends import default_backend
import base64
import sys
def generate_key(password: str) -> bytes:
"""Generate an AES key from a password."""
# Using a fixed key for testing (similar to PowerShell script)
key = "Q5KyUru6wn82hlY9k8xUjJOPIC9da41jgRkpt21jo2L="
return base64.b64decode(key)
def encrypt_file(file_path: str, key: bytes) -> bool:
"""Encrypt a single file using AES."""
try:
# Read the original file
with open(file_path, 'rb') as file:
file_data = file.read()
# Create an initialization vector
iv = os.urandom(16)
# Create AES cipher
cipher = Cipher(
algorithms.AES(key),
modes.CBC(iv),
backend=default_backend()
)
encryptor = cipher.encryptor()
# Add padding
padder = padding.PKCS7(128).padder()
padded_data = padder.update(file_data) + padder.finalize()
# Encrypt the data
encrypted_data = encryptor.update(padded_data) + encryptor.finalize()
# Write the encrypted data to a new file
encrypted_path = f"{file_path}.encrypted"
with open(encrypted_path, 'wb') as file:
# Write IV first, then encrypted data
file.write(iv)
file.write(encrypted_data)
# Remove the original file
os.remove(file_path)
return True
except Exception as e:
logging.error(f"Failed to encrypt {file_path}: {str(e)}")
return False
def encrypt_directory(base_dir: str, password: str):
"""Encrypt all files in the specified directory."""
try:
# Generate encryption key
key = generate_key(password)
# Define target file extensions (same as PowerShell script)
target_extensions = [
'.pdf', '.xls', '.xlsx', '.ppt', '.pptx', '.doc', '.docx',
'.rtf', '.txt', '.csv', '.jpg', '.jpeg', '.png', '.gif',
'.avi', '.midi', '.mov', '.mp3', '.mp4', '.mpeg', '.mpg', '.ogg'
]
# Get list of files to encrypt
files = []
for root, _, filenames in os.walk(base_dir):
for filename in filenames:
if any(filename.lower().endswith(ext) for ext in target_extensions) and \
not filename.endswith('.encrypted'):
files.append(os.path.join(root, filename))
total_files = len(files)
if total_files == 0:
logging.info("No files found to encrypt")
return
logging.info(f"Found {total_files} files to encrypt")
# Track progress
successful = 0
failed = 0
# Process each file
for i, file_path in enumerate(files, 1):
logging.info(f"Encrypting {file_path}")
if encrypt_file(file_path, key):
successful += 1
else:
failed += 1
# Log progress every 100 files or at the end
if i % 100 == 0 or i == total_files:
logging.info(f"Processed {i}/{total_files} files...")
# Log final results
logging.info("Encryption complete!")
logging.info(f"Successfully encrypted: {successful} files")
if failed > 0:
logging.warning(f"Failed to encrypt: {failed} files")
except Exception as e:
logging.error(f"Encryption failed: {str(e)}")
raise
def main():
parser = argparse.ArgumentParser(description="Encrypt files in directory")
parser.add_argument("--base_dir", default="~/encryption_test",
help="Base directory containing files to encrypt (default: ~/encryption_test)")
parser.add_argument("--password", required=True,
help="Password for encryption")
args = parser.parse_args()
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
# Expand user path (~/...)
base_dir = os.path.expanduser(args.base_dir)
# Verify directory exists
if not os.path.isdir(base_dir):
logging.error(f"Directory not found: {base_dir}")
sys.exit(1)
encrypt_directory(base_dir, args.password)
if __name__ == "__main__":
main()
+41
View File
@@ -0,0 +1,41 @@
# 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.
"""
Zerto Reports Example
Note: The reports functionality is demonstrated in vpg_failover_example.py, which includes:
1. Recovery Reports:
- Getting recovery reports for VPGs
- Filtering reports by date range
- Getting specific recovery operation details
- Getting latest failover test reports
2. Resource Reports:
- Getting resource reports with various filters
- Filtering by site, cluster, and organization
- Getting detailed resource information
Please refer to vpg_failover_example.py for practical examples of using the reports functionality.
You can run vpg_failover_example.py with:
python vpg_failover_example.py \
--zvm_address <zvm_ip> \
--client_id <client_id> \
--client_secret <client_secret> \
--ignore_ssl
For more information about the reports API, see the RecoveryReports class in zvml/recovery_reports.py
"""
if __name__ == "__main__":
print(__doc__)
+98
View File
@@ -0,0 +1,98 @@
# 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.
"""
Zerto Server Date-Time Example Script
This script demonstrates how to retrieve server time information in different formats from a Zerto Virtual Manager (ZVM).
The script performs the following steps:
1. Connects to Zerto Virtual Manager (ZVM)
2. Retrieves server time in three different formats:
- Local time
- UTC time
- Argument format (used for API parameters)
3. Displays the time information for each format
Required Arguments:
--zvm_address: ZVM address
--client_id: Keycloak client ID
--client_secret: Keycloak client secret
Optional Arguments:
--ignore_ssl: Ignore SSL certificate verification
Example Usage:
python examples/server_date_time_example.py \
--zvm_address <zvm_address> \
--client_id <client_id> \
--client_secret <client_secret> \
--ignore_ssl
"""
import sys
import os
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import argparse
import logging
import urllib3
from zvml import ZVMLClient
from zvml.server_date_time import DateTimeFormat
# Disable SSL warnings
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
def main():
parser = argparse.ArgumentParser(description="Zerto Server Date-Time Example")
parser.add_argument("--zvm_address", required=True, help="ZVM address")
parser.add_argument('--client_id', required=True, help='Keycloak client ID')
parser.add_argument('--client_secret', required=True, help='Keycloak client secret')
parser.add_argument("--ignore_ssl", action="store_true", help="Ignore SSL certificate verification")
args = parser.parse_args()
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
try:
# Connect to ZVM
logging.info(f"Connecting to ZVM at {args.zvm_address}")
client = ZVMLClient(
zvm_address=args.zvm_address,
client_id=args.client_id,
client_secret=args.client_secret,
verify_certificate=not args.ignore_ssl
)
# Test all three date-time formats
logging.info("\nTesting all server date-time formats:")
# Get local time
local_time = client.server_date_time.get_server_date_time(DateTimeFormat.LOCAL)
logging.info(f"\nLocal Time: {local_time}")
# Get UTC time
utc_time = client.server_date_time.get_server_date_time(DateTimeFormat.UTC)
logging.info(f"UTC Time: {utc_time}")
# Get argument format
arg_format = client.server_date_time.get_server_date_time(DateTimeFormat.ARGUMENT)
logging.info(f"Argument Format: {arg_format}")
except Exception as e:
logging.error(f"Error occurred: {e}")
raise
if __name__ == "__main__":
main()
+111
View File
@@ -0,0 +1,111 @@
# 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.
"""
Zerto Service Profiles Example Script
This script demonstrates how to retrieve and display service profile information from a Zerto Virtual Manager (ZVM).
The script performs the following steps:
1. Connects to Zerto Virtual Manager (ZVM)
2. Retrieves service profiles (optionally filtered by site)
3. Displays detailed information for each profile:
- Profile name
- RPO settings
- History configuration
- Journal size limits
- Test intervals
- Profile description
Required Arguments:
--zvm_address: ZVM address
--client_id: Keycloak client ID
--client_secret: Keycloak client secret
Optional Arguments:
--site_identifier: Site identifier to filter profiles
--ignore_ssl: Ignore SSL certificate verification
Example Usage:
python examples/service_profiles_example.py \
--zvm_address <zvm_address> \
--client_id <client_id> \
--client_secret <client_secret> \
--site_identifier <site_id> \
--ignore_ssl
"""
import sys
import os
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import argparse
import logging
import urllib3
import json
from zvml import ZVMLClient
# Disable SSL warnings
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
def main():
parser = argparse.ArgumentParser(description="Zerto Service Profiles Example")
parser.add_argument("--zvm_address", required=True, help="ZVM address")
parser.add_argument('--client_id', required=True, help='Keycloak client ID')
parser.add_argument('--client_secret', required=True, help='Keycloak client secret')
parser.add_argument("--site_identifier", help="Optional site identifier to filter profiles")
parser.add_argument("--ignore_ssl", action="store_true", help="Ignore SSL certificate verification")
args = parser.parse_args()
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
try:
# Connect to ZVM
logging.info(f"Connecting to ZVM at {args.zvm_address}")
client = ZVMLClient(
zvm_address=args.zvm_address,
client_id=args.client_id,
client_secret=args.client_secret,
verify_certificate=not args.ignore_ssl
)
# Get all service profiles
logging.info("\nFetching service profiles...")
profiles = client.service_profiles.get_service_profiles(
site_identifier=args.site_identifier
)
# Display service profiles information
if profiles:
logging.info(f"\nFound {len(profiles)} service profiles:")
for profile in profiles:
logging.info("\nService Profile Details:")
logging.info(f"Name: {profile.get('serviceProfileName')}")
logging.info(f"RPO: {profile.get('rpo')}")
logging.info(f"History: {profile.get('history')}")
logging.info(f"Max Journal Size: {profile.get('maxJournalSizeInPercent')}%")
logging.info(f"Test Interval: {profile.get('testInterval')}")
if profile.get('description'):
logging.info(f"Description: {profile.get('description')}")
logging.info("-" * 50)
else:
logging.warning("No service profiles found")
except Exception as e:
logging.error(f"Error occurred: {e}")
raise
if __name__ == "__main__":
main()
+156
View File
@@ -0,0 +1,156 @@
#!/usr/bin/env python3
# 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.
"""
Zerto Tweaks Management Example Script
This script demonstrates how to manage Zerto system tweaks, which are advanced configuration settings.
The script performs the following steps:
1. Connects to Zerto Virtual Manager (ZVM)
2. Lists all available system tweaks
3. Sets a specific tweak value (t_ransomwareEngCuSumThrsDiff)
4. Displays the updated tweak details
5. Deletes the tweak setting
6. Verifies the deletion by listing tweaks again
Required Arguments:
--zvm_address: ZVM address
--client_id: Keycloak client ID
--client_secret: Keycloak client secret
Optional Arguments:
--ignore_ssl: Ignore SSL certificate verification
Example Usage:
python examples/tweaks_example.py \
--zvm_address <zvm_address> \
--client_id <client_id> \
--client_secret <client_secret> \
--ignore_ssl
"""
import argparse
import logging
import urllib3
import json
import sys
import os
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from zvml.zvml import ZVMLClient
from zvml.tweaks import Tweaks
from zvml.common import ZertoTweakType
# Disable SSL warnings
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
def setup_client(args):
"""Initialize and return Zerto client"""
client = ZVMLClient(
zvm_address=args.zvm_address,
client_id=args.client_id,
client_secret=args.client_secret,
verify_certificate=not args.ignore_ssl
)
return client
def format_tweaks_table(result):
"""Format tweaks into a table"""
# Calculate maximum lengths for formatting
max_name_len = max(len(str(tweak.get('name', ''))) for tweak in result)
max_value_len = max(len(str(tweak.get('value', ''))) for tweak in result)
max_type_len = max(len(str(tweak.get('type', ''))) for tweak in result)
max_comment_len = max(len(str(tweak.get('comment', ''))) for tweak in result)
# Print header
header = f"{'Name':<{max_name_len}} | {'Value':<{max_value_len}} | {'Type':<{max_type_len}} | Description | {'Comment':<{max_comment_len}}"
logging.info(header)
logging.info("-" * len(header))
# Print tweaks in a formatted table
for tweak in result:
name = str(tweak.get('name', 'N/A'))
value = str(tweak.get('value', 'N/A'))
tweak_type = str(tweak.get('type', 'N/A'))
description = str(tweak.get('description', 'No description available'))
comment = str(tweak.get('comment', ''))
logging.info(f"{name:<{max_name_len}} | {value:<{max_value_len}} | {tweak_type:<{max_type_len}} | {description} | {comment}")
logging.info("-" * 80)
def manage_tweaks(client: ZVMLClient):
"""List and manage ZVM tweaks"""
tweaks = Tweaks(client)
# Set a specific tweak
tweak_name = "t_ransomwareEngCuSumThrsDiff"
logging.info(f"\nSetting tweak {tweak_name}:")
updated_tweak = tweaks.set_tweak(
tweak_name=tweak_name,
value="5",
tweak_type=ZertoTweakType.ZVM,
comment="mycomment"
)
# List all tweaks
logging.info("\nListing all tweaks:")
result = tweaks.list_tweaks()
logging.info(f"Found {len(result)} ZVM tweaks:")
logging.info("-" * 80)
format_tweaks_table(result)
# Show the specific tweak
logging.info("\nShowing specific tweak details:")
specific_result = tweaks.list_tweaks(tweak_name=tweak_name)
format_tweaks_table(specific_result)
# Delete the tweak
logging.info(f"\nDeleting tweak {tweak_name}:")
tweaks.delete_tweak(tweak_name)
# Verify deletion by listing all tweaks again
logging.info("\nVerifying deletion - listing all tweaks:")
result = tweaks.list_tweaks()
logging.info(f"Found {len(result)} ZVM tweaks:")
logging.info("-" * 80)
format_tweaks_table(result)
def main():
parser = argparse.ArgumentParser(description="ZVM Tweaks Management Example")
parser.add_argument("--zvm_address", required=True, help="ZVM address")
parser.add_argument('--client_id', required=True, help='Keycloak client ID')
parser.add_argument('--client_secret', required=True, help='Keycloak client secret')
parser.add_argument("--ignore_ssl", action="store_true", help="Ignore SSL certificate verification")
args = parser.parse_args()
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
try:
# Setup client
client = setup_client(args)
# Manage tweaks
manage_tweaks(client)
except Exception as e:
logging.exception("Error occurred:")
sys.exit(1)
if __name__ == "__main__":
main()
+230
View File
@@ -0,0 +1,230 @@
#!/usr/bin/python3
# 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.
"""
Zerto VPG Bulk Update Example Script
This script demonstrates how to update multiple Virtual Protection Groups (VPGs) settings in bulk.
The script performs the following steps:
1. Connects to Zerto Virtual Manager (ZVM)
2. Lists peer sites and their resources:
- Available datastores
- Available networks
3. Retrieves current VPG settings for all VPGs
4. Prompts for new settings:
- Target datastore
- Failover network
- Test network
5. Updates all VPGs with the new settings after confirmation
Required Arguments:
--zvm_address: Site 1 ZVM address
--client_id: Site 1 Keycloak client ID
--client_secret: Site 1 Keycloak client secret
Optional Arguments:
--ignore_ssl: Ignore SSL certificate verification
Example Usage:
python examples/update_existing_vpgs.py \
--zvm_address <zvm_address> \
--client_id <client_id> \
--client_secret <client_secret> \
--ignore_ssl
"""
import argparse
import logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
import urllib3
import sys
import os
import json
from typing import Dict, List
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from zvml import ZVMLClient
# Disable SSL warnings
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
def setup_client(args):
"""Initialize and return Zerto client"""
client = ZVMLClient(
zvm_address=args.zvm_address,
client_id=args.client_id,
client_secret=args.client_secret,
verify_certificate=not args.ignore_ssl
)
return client
def print_site_resources(client, site_identifier: str, site_name: str):
"""Print site resources in a visual way"""
print(f"\nSite: {site_name}")
print(f"Site ID: {site_identifier}")
# Get and print datastores
print("\nDatastores:")
datastores = client.virtualization_sites.get_virtualization_site_datastores(site_identifier)
datastore_map = {}
for idx, ds in enumerate(datastores, 1):
datastore_map[idx] = ds['DatastoreIdentifier']
name = ds.get('DatastoreName', 'N/A')
logical_name = ds.get('LogicalName', 'N/A')
print(f" {idx}. ID: {ds['DatastoreIdentifier']}")
print(f" Name: {name}")
print(f" Logical Name: {logical_name}")
# Get and print networks
print("\nNetworks:")
networks = client.virtualization_sites.get_virtualization_site_networks(site_identifier)
network_map = {}
for idx, net in enumerate(networks, 1):
network_map[idx] = net['NetworkIdentifier']
name = net.get('VirtualizationNetworkName', 'N/A')
print(f" {idx}. ID: {net['NetworkIdentifier']}")
print(f" Name: {name}")
return datastore_map, network_map
def get_vpg_settings(client, vpgs: List[Dict]):
"""Get current VPG settings"""
vpg_settings = []
for vpg in vpgs:
vpg_id = vpg['VpgIdentifier']
vpg_name = vpg['VpgName']
# Create new settings based on existing VPG
settings_id = client.vpgs.create_vpg_settings(
basic=None,
journal=None,
recovery=None,
networks=None,
vpg_identifier=vpg_id
)
# Get the settings details
settings = client.vpgs.get_vpg_settings_by_id(vpg_settings_id=settings_id)
vpg_settings.append({
'vpg_name': vpg_name,
'vpg_id': vpg_id,
'settings_id': settings_id,
'current_settings': settings,
'default_datastore': settings.get('Recovery', {}).get('DefaultDatastoreIdentifier'),
'failover_network': settings.get('Networks', {}).get('Failover', {}).get('Hypervisor', {}).get('DefaultNetworkIdentifier'),
'test_network': settings.get('Networks', {}).get('FailoverTest', {}).get('Hypervisor', {}).get('DefaultNetworkIdentifier')
})
print(f"\nVPG: {vpg_name}")
print(f" Current Datastore: {settings.get('Recovery', {}).get('DefaultDatastoreIdentifier')}")
print(f" Current Failover Network: {settings.get('Networks', {}).get('Failover', {}).get('Hypervisor', {}).get('DefaultNetworkIdentifier')}")
print(f" Current Test Network: {settings.get('Networks', {}).get('FailoverTest', {}).get('Hypervisor', {}).get('DefaultNetworkIdentifier')}")
return vpg_settings
def update_vpg_settings(client, vpg_settings: List[Dict], new_datastore: str, new_failover_network: str, new_test_network: str):
"""Update all VPG settings with new values"""
for vpg in vpg_settings:
settings = vpg['current_settings']
# Update datastore
settings['Recovery']['DefaultDatastoreIdentifier'] = new_datastore
# Update networks
if 'Networks' not in settings:
settings['Networks'] = {'Failover': {'Hypervisor': {}}, 'FailoverTest': {'Hypervisor': {}}}
settings['Networks']['Failover']['Hypervisor']['DefaultNetworkIdentifier'] = new_failover_network
settings['Networks']['FailoverTest']['Hypervisor']['DefaultNetworkIdentifier'] = new_test_network
# Update the settings
client.vpgs.update_vpg_settings(vpg_settings_id=vpg['settings_id'], payload=settings)
# Commit the changes
client.vpgs.commit_vpg(vpg['settings_id'], vpg['vpg_name'], sync=False)
print(f"Updated and committed settings for VPG: {vpg['vpg_name']}")
def main():
parser = argparse.ArgumentParser(description="Update existing VPGs settings")
parser.add_argument("--zvm_address", required=True, help="Site 1 ZVM address")
parser.add_argument('--client_id', required=True, help='Site 1 Keycloak client ID')
parser.add_argument('--client_secret', required=True, help='Site 1 Keycloak client secret')
parser.add_argument("--ignore_ssl", action="store_true", help="Ignore SSL certificate verification")
args = parser.parse_args()
try:
# Setup client
client = setup_client(args)
# Get peer sites
peer_sites = client.peersites.get_peer_sites()
logging.debug(f"Peer sites: {peer_sites}")
if not peer_sites:
raise ValueError("No peer sites found")
# Get the first peer site
peer_site = peer_sites[0]
peer_site_id = peer_site['SiteIdentifier']
peer_site_name = peer_site.get('PeerSiteName')
# Print resources and get mapping for peer site
datastore_map, network_map = print_site_resources(client, peer_site_id, peer_site_name)
# Get and print current VPG settings
vpgs = client.vpgs.list_vpgs()
vpg_settings = get_vpg_settings(client, vpgs)
logging.debug(f"VPG settings: {json.dumps(vpg_settings, indent=4)}")
# Get user input
print("\nEnter sequential numbers for new settings:")
ds_num = int(input("Datastore number: "))
fo_net_num = int(input("Failover network number: "))
test_net_num = int(input("Failover test network number: "))
# Validate input
if not all(num in datastore_map for num in [ds_num]) or \
not all(num in network_map for num in [fo_net_num, test_net_num]):
raise ValueError("Invalid sequential number entered")
# Get actual IDs
new_datastore = datastore_map[ds_num]
new_failover_network = network_map[fo_net_num]
new_test_network = network_map[test_net_num]
# Confirm with user
print(f"\nAbout to update all VPGs with:")
print(f"New datastore: {new_datastore}")
print(f"New failover network: {new_failover_network}")
print(f"New test network: {new_test_network}")
if input("\nContinue? (y/n): ").lower() != 'y':
print("Operation cancelled")
return
# Update all VPGs
update_vpg_settings(client, vpg_settings, new_datastore, new_failover_network, new_test_network)
print("\nAll VPGs have been updated successfully")
except Exception as e:
logging.exception("Error occurred:")
sys.exit(1)
if __name__ == "__main__":
main()
+282
View File
@@ -0,0 +1,282 @@
# 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.
"""
Zerto Virtualization Sites Example Script
This script demonstrates how to retrieve and manage virtualization site information from Zerto.
The script performs the following steps:
1. Connects to Zerto Virtual Manager (ZVM)
2. Retrieves information about virtualization sites:
- Basic site details
- Unprotected VMs and vApps
- Storage resources (datastores, clusters)
- Network configurations
- Host information
- Cloud resources (networks, subnets, security)
3. For each site, retrieves detailed information about:
- Organization VDCs
- Storage policies
- Network configurations
- Host devices and clusters
Required Arguments:
--zvm_address: ZVM address
--client_id: Keycloak client ID
--client_secret: Keycloak client secret
Optional Arguments:
--ignore_ssl: Ignore SSL certificate verification
Example Usage:
python examples/virtualization_sites_example.py \
--zvm_address <zvm_address> \
--client_id <client_id> \
--client_secret <client_secret> \
--ignore_ssl
"""
import sys
import os
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import argparse
import logging
import urllib3
import json
from zvml import ZVMLClient
# Disable SSL warnings
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
def main():
parser = argparse.ArgumentParser(description="Zerto Virtualization Sites Example")
parser.add_argument("--zvm_address", required=True, help="ZVM IP address")
parser.add_argument('--client_id', required=True, help='Keycloak client ID')
parser.add_argument('--client_secret', required=True, help='Keycloak client secret')
parser.add_argument("--ignore_ssl", action="store_true", help="Ignore SSL certificate verification")
args = parser.parse_args()
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
try:
# Initialize the client
client = ZVMLClient(
zvm_address=args.zvm_address,
client_id=args.client_id,
client_secret=args.client_secret,
verify_certificate=not args.ignore_ssl
)
# Example 1: Get all virtualization sites
logging.info("\nExample 1: Getting all virtualization sites")
sites = client.virtualization_sites.get_virtualization_sites()
logging.info("All sites:")
logging.info(json.dumps(sites, indent=2))
# Example 2: Get details for each site individually
for site in sites:
site_id = site['SiteIdentifier']
site_name = site['VirtualizationSiteName']
logging.info(f"\nExample 2: Getting details for site {site_name} (ID: {site_id})")
site_details = client.virtualization_sites.get_virtualization_sites(site_identifier=site_id)
logging.info("Site Details:")
logging.info(json.dumps(site_details, indent=2))
# Example 3: Get unprotected VMs for each site
logging.info(f"\nExample 3: Getting unprotected VMs for site {site_name}")
vms = client.virtualization_sites.get_virtualization_site_vms(site_id)
logging.info(f"Found {len(vms)} unprotected VMs:")
logging.info(json.dumps(vms, indent=2))
# Example 4: Get unprotected VCD vApps for each site
logging.info(f"\nExample 4: Getting unprotected VCD vApps for site {site_name}")
vapps = client.virtualization_sites.get_virtualization_site_vcd_vapps(site_id)
logging.info(f"Found {len(vapps)} unprotected VCD vApps:")
logging.info(json.dumps(vapps, indent=2))
# Example 5: Get datastores for each site
logging.info(f"\nExample 5: Getting datastores for site {site_name}")
datastores = client.virtualization_sites.get_virtualization_site_datastores(site_id)
logging.info(f"Found {len(datastores)} datastores:")
logging.info(json.dumps(datastores, indent=2))
# Example 6: Get folders for each site
logging.info(f"\nExample 6: Getting folders for site {site_name}")
folders = client.virtualization_sites.get_virtualization_site_folders(site_id)
logging.info(f"Found {len(folders)} folders:")
logging.info(json.dumps(folders, indent=2))
# Example 7: Get datastore clusters for each site
logging.info(f"\nExample 7: Getting datastore clusters for site {site_name}")
datastore_clusters = client.virtualization_sites.get_virtualization_site_datastore_clusters(site_id)
logging.info(f"Found {len(datastore_clusters)} datastore clusters:")
logging.info(json.dumps(datastore_clusters, indent=2))
# Example 8: Get resource pools for each site
logging.info(f"\nExample 8: Getting resource pools for site {site_name}")
resource_pools = client.virtualization_sites.get_virtualization_site_resource_pools(site_id)
logging.info(f"Found {len(resource_pools)} resource pools:")
logging.info(json.dumps(resource_pools, indent=2))
# Example 9: Get organization VDCs for each site
logging.info(f"\nExample 9: Getting organization VDCs for site {site_name}")
org_vdcs = client.virtualization_sites.get_virtualization_site_org_vdcs(site_id)
logging.info(f"Found {len(org_vdcs)} organization VDCs:")
logging.info(json.dumps(org_vdcs, indent=2))
# Example 10: Get networks for each site
logging.info(f"\nExample 10: Getting networks for site {site_name}")
networks = client.virtualization_sites.get_virtualization_site_networks(site_id)
logging.info(f"Found {len(networks)} networks:")
logging.info(json.dumps(networks, indent=2))
# Example 11: Get hosts for each site
logging.info(f"\nExample 11: Getting hosts for site {site_name}")
hosts = client.virtualization_sites.get_virtualization_site_hosts(site_id)
logging.info(f"Found {len(hosts)} hosts:")
logging.info(json.dumps(hosts, indent=2))
# Example 12: Get host clusters for each site
logging.info(f"\nExample 12: Getting host clusters for site {site_name}")
host_clusters = client.virtualization_sites.get_virtualization_site_host_clusters(site_id)
logging.info(f"Found {len(host_clusters)} host clusters:")
logging.info(json.dumps(host_clusters, indent=2))
# Example 13: Get repositories for each site
logging.info(f"\nExample 13: Getting repositories for site {site_name}")
repositories = client.virtualization_sites.get_virtualization_site_repositories(site_id)
logging.info(f"Found {len(repositories)} repositories:")
logging.info(json.dumps(repositories, indent=2))
# Example 14: Get networks for each org VDC
logging.info(f"\nExample 14: Getting org VDC networks for site {site_name}")
org_vdcs = client.virtualization_sites.get_virtualization_site_org_vdcs(site_id)
for org_vdc in org_vdcs:
org_vdc_id = org_vdc['OrgVdcIdentifier']
org_vdc_name = org_vdc['VcdVdcName']
logging.info(f"\nFetching networks for org VDC: {org_vdc_name}")
networks = client.virtualization_sites.get_virtualization_site_org_vdc_networks(site_id, org_vdc_id)
logging.info(f"Found {len(networks)} networks in org VDC {org_vdc_name}:")
logging.info(json.dumps(networks, indent=2))
# Example 15: Get storage policies for each org VDC
logging.info(f"\nExample 15: Getting storage policies for org VDC: {org_vdc_name}")
storage_policies = client.virtualization_sites.get_virtualization_site_org_vdc_storage_policies(site_id, org_vdc_id)
logging.info(f"Found {len(storage_policies)} storage policies in org VDC {org_vdc_name}:")
logging.info(json.dumps(storage_policies, indent=2))
# Example 16: Get devices for each site
logging.info(f"\nExample 16a: Getting all devices for site {site_name}")
devices = client.virtualization_sites.get_virtualization_site_devices(site_id)
logging.info(f"Found {len(devices)} devices:")
logging.info(json.dumps(devices, indent=2))
# If we have hosts, get devices for the first host
if hosts:
host_id = hosts[0]['HostIdentifier']
logging.info(f"\nExample 16b: Getting devices for host {host_id} in site {site_name}")
host_devices = client.virtualization_sites.get_virtualization_site_devices(
site_id,
host_identifier=host_id
)
logging.info(f"Found {len(host_devices)} devices for host {host_id}:")
logging.info(json.dumps(host_devices, indent=2))
# If we found any devices, try filtering by the first device name
if host_devices:
device_name = host_devices[0]['DeviceName']
logging.info(f"\nExample 16c: Getting devices with name {device_name} in site {site_name}")
filtered_devices = client.virtualization_sites.get_virtualization_site_devices(
site_id,
device_name=device_name
)
logging.info(f"Found {len(filtered_devices)} devices with name {device_name}:")
logging.info(json.dumps(filtered_devices, indent=2))
# Example 17: Get public cloud virtual networks for each site
logging.info(f"\nExample 17: Getting public cloud virtual networks for site {site_name}")
cloud_networks = client.virtualization_sites.get_virtualization_site_public_cloud_networks(site_id)
logging.info(f"Found {len(cloud_networks)} public cloud virtual networks:")
logging.info(json.dumps(cloud_networks, indent=2))
# Example 18: Get public cloud subnets for each site
logging.info(f"\nExample 18: Getting public cloud subnets for site {site_name}")
cloud_subnets = client.virtualization_sites.get_virtualization_site_public_cloud_subnets(site_id)
logging.info(f"Found {len(cloud_subnets)} public cloud subnets:")
logging.info(json.dumps(cloud_subnets, indent=2))
# Example 19: Get public cloud security groups for each site
logging.info(f"\nExample 19: Getting public cloud security groups for site {site_name}")
security_groups = client.virtualization_sites.get_virtualization_site_public_cloud_security_groups(site_id)
logging.info(f"Found {len(security_groups)} public cloud security groups:")
logging.info(json.dumps(security_groups, indent=2))
# Example 20: Get public cloud VM instance types for each site
logging.info(f"\nExample 20: Getting public cloud VM instance types for site {site_name}")
instance_types = client.virtualization_sites.get_virtualization_site_public_cloud_vm_instance_types(site_id)
logging.info(f"Found {len(instance_types)} public cloud VM instance types:")
logging.info(json.dumps(instance_types, indent=2))
# Example 21: Get public cloud resource groups for each site
# currently this API returns a 500 error
# logging.info(f"\nExample 21: Getting public cloud resource groups for site {site_name}")
# resource_groups = client.virtualization_sites.get_virtualization_site_public_cloud_resource_groups(site_id)
# logging.info(f"Found {len(resource_groups)} public cloud resource groups:")
# logging.info(json.dumps(resource_groups, indent=2))
# Example 22: Get public cloud keys containers for each site
# currently this API returns a 500 error
# logging.info(f"\nExample 22: Getting public cloud keys containers for site {site_name}")
# keys_containers = client.virtualization_sites.get_virtualization_site_public_cloud_keys_containers(site_id)
# logging.info(f"Found {len(keys_containers)} public cloud keys containers:")
# logging.info(json.dumps(keys_containers, indent=2))
# Example 23: Get all encryption keys
# currently this API returns a 500 error if does not exist
# logging.info(f"\nExample 23a: Getting all encryption keys for site {site_name}")
# encryption_keys = client.virtualization_sites.get_virtualization_site_public_cloud_encryption_keys(site_id)
# logging.info(f"Found {len(encryption_keys)} encryption keys:")
# logging.info(json.dumps(encryption_keys, indent=2))
# Example 23b: Get details of specific encryption keys if any exist
# if encryption_keys:
# key_id = encryption_keys[0]['Id']
# logging.info(f"\nExample 23b: Getting details for encryption key {key_id}")
# key_details = client.virtualization_sites.get_virtualization_site_public_cloud_encryption_keys(site_id, key_id)
# logging.info("Encryption key details:")
# logging.info(json.dumps(key_details, indent=2))
# Example 24: Get public cloud managed identities for each site
# currently this API returns a 500 error if does not exist
# logging.info(f"\nExample 24: Getting public cloud managed identities for site {site_name}")
# managed_identities = client.virtualization_sites.get_virtualization_site_public_cloud_managed_identities(site_id)
# logging.info(f"Found {len(managed_identities)} managed identities:")
# logging.info(json.dumps(managed_identities, indent=2))
# Example 25: Get public cloud disk encryption keys for each site
# currently this API returns a 500 error if does not exist
# logging.info(f"\nExample 25: Getting public cloud disk encryption keys for site {site_name}")
# disk_encryption_keys = client.virtualization_sites.get_virtualization_site_public_cloud_disk_encryption_keys(site_id)
# logging.info(f"Found {len(disk_encryption_keys)} disk encryption keys:")
# logging.info(json.dumps(disk_encryption_keys, indent=2))
except Exception as e:
logging.error(f"Error occurred: {e}")
raise
if __name__ == "__main__":
main()
+243
View File
@@ -0,0 +1,243 @@
#!/usr/bin/python3
# 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.
"""
Zerto Virtual Machines Example Script
This script demonstrates how to manage and retrieve information about protected virtual machines in Zerto.
The script performs the following steps:
1. Connects to Zerto Virtual Manager (ZVM)
2. Gets site information and resources:
- Local and peer site details
- Available datastores
- Network configurations
3. Demonstrates VM operations:
- Lists all protected VMs
- Gets detailed information for specific VMs
- Filters VMs by VPG name
- Manages VM restore points:
* Lists available checkpoints
* Gets points in time
* Retrieves recovery statistics
4. Shows VM restore capabilities:
- Configures restore settings
- Handles network and storage mappings
- Manages restore operations
Required Arguments:
--zvm_address: ZVM address
--client_id: Keycloak client ID
--client_secret: Keycloak client secret
Optional Arguments:
--ignore_ssl: Ignore SSL certificate verification
Example Usage:
python examples/vms_example.py \
--zvm_address <zvm_address> \
--client_id <client_id> \
--client_secret <client_secret> \
--ignore_ssl
Note: VM restore functionality is commented out in this example as it may return a 500 error.
"""
# NOTE
# this example assumes that at least one VPG exists on the ZVM and protected VMs exist in the VPG
# the vm restore is commnted out as it fails with a 500
import sys
import os
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import argparse
import logging
import urllib3
import json
from zvml import ZVMLClient
# Disable SSL warnings
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
def main():
parser = argparse.ArgumentParser(description="Zerto Virtual Machines Example")
parser.add_argument("--zvm_address", required=True, help="ZVM address")
parser.add_argument('--client_id', required=True, help='Keycloak client ID')
parser.add_argument('--client_secret', required=True, help='Keycloak client secret')
parser.add_argument("--ignore_ssl", action="store_true", help="Ignore SSL certificate verification")
args = parser.parse_args()
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
try:
# Connect to ZVM
logging.info(f"Connecting to ZVM at {args.zvm_address}")
client = ZVMLClient(
zvm_address=args.zvm_address,
client_id=args.client_id,
client_secret=args.client_secret,
verify_certificate=not args.ignore_ssl
)
# Get both sites information
sites = client.virtualization_sites.get_virtualization_sites()
logging.info(f"Found {len(sites)} sites:")
for site in sites:
logging.info(f"Site: {site['VirtualizationSiteName']} (ID: {site['SiteIdentifier']})")
# Get the peer (second) site identifier
local_site = client.localsite.get_local_site()
peer_site = next(site for site in sites
if site['SiteIdentifier'] != local_site['SiteIdentifier'])
peer_site_id = peer_site['SiteIdentifier']
logging.info(f"Peer site identifier: {peer_site_id}")
# Get datastores from peer site
datastores = client.virtualization_sites.get_virtualization_site_datastores(peer_site_id)
if not datastores:
raise ValueError("No datastores found in peer site")
# logging.info(json.dumps(datastores, indent=2))
datastore_id = datastores[0]['DatastoreIdentifier'] # Use first datastore
logging.info(f"Selected datastore ID from peer site: {datastore_id}")
# Get networks from peer site
networks = client.virtualization_sites.get_virtualization_site_networks(peer_site_id)
if not networks:
raise ValueError("No networks found in peer site")
network_id = networks[0]['NetworkIdentifier'] # Use first network
logging.info(f"Selected network ID from peer site: {network_id}")
# Example 1: Get all protected VMs
logging.info("\nExample 1: Getting all protected VMs")
vms = client.vms.list_vms()
# logging.info(f'vms: {json.dumps(vms, indent=2)}')
if len(vms) == 0:
raise ValueError("No protected VMs found")
# Example 2: If we found any VMs, get details for the first one
first_vm = vms[0]
vm_id = first_vm['VmIdentifier']
vpg_id = first_vm.get('VpgIdentifier') # VpgIdentifier might be optional
logging.info(f"\nExample 2: Getting details for VM {vm_id}")
vm_details = client.vms.list_vms(
vm_identifier=vm_id,
vpg_identifier=vpg_id
)
# logging.info("VM details:")
# logging.info(json.dumps(vm_details, indent=2))
# Example 3: Get VMs filtered by VPG name
vpg_name = first_vm.get('VpgName')
if vpg_name:
logging.info(f"\nExample 3: Getting VMs filtered by VPG name: {vpg_name}")
filtered_vms = client.vms.list_vms(vpg_name=vpg_name)
# logging.info(f"Found {len(filtered_vms)} VMs in VPG {vpg_name}:")
# logging.info(json.dumps(filtered_vms, indent=2))
# Example 4: Restore the VM if it has checkpoints
logging.info("\nExample 4: Restoring VM from checkpoint")
vpg_info = client.vpgs.list_vpgs(vpg_identifier=vpg_id)
vpg_name = vpg_info['VpgName']
checkpoints = client.vpgs.list_checkpoints(vpg_name=vpg_name)
# logging.info(f"checkpoints: {json.dumps(checkpoints, indent=2)}")
if not checkpoints:
logging.warning("No checkpoints found for VPG")
raise ValueError("No checkpoints found for VPG")
checkpoint_id = checkpoints[0]['CheckpointIdentifier']
# Prepare restore settings using IDs from peer site
restore_settings = {
"datastoreIdentifier": datastore_id,
"nics": [
{
"hypervisor": {
"dnsSuffix": "",
"ipConfig": {
"gateway": "",
"isDhcp": True,
"primaryDns": "",
"secondaryDns": "",
"staticIp": "",
"subnetMask": ""
},
"networkIdentifier": network_id,
"shouldReplaceMacAddress": True
},
"nicIdentifier": first_vm['Nics'][0]['NicIdentifier']
}
],
"volumes": [
{
"datastore": {
"datastoreIdentifier": datastore_id,
"isThin": True
},
"volumeIdentifier": volume['VmVolumeIdentifier']
} for volume in first_vm['Volumes']
]
}
# Initiate VM restore
# temporary commented out as it fails with a 500
# logging.info(f"Restoring VM {first_vm['VmName']} from checkpoint {checkpoint_id}")
# restore_result = client.vms.restore_vm(
# vm_identifier=vm_id,
# vpg_identifier=vpg_id,
# restored_vm_name=f"{first_vm['VmName']}_restored",
# checkpoint_identifier=checkpoint_id,
# journal_vm_restore_settings=restore_settings
# )
# logging.info(f"Restore initiated: {json.dumps(restore_result, indent=2)}")
# Example 7: Get points in time for a VM
logging.info("\nExample 7: Getting points in time for VM")
try:
# You can optionally specify start_date and end_date in ISO format
# e.g., "2024-01-01T00:00:00.000Z"
points_in_time = client.vms.list_vm_points_in_time(
vm_identifier=vm_id,
vpg_identifier=vpg_id
)
logging.info(f"Found {len(points_in_time)} points in time:")
logging.info(json.dumps(points_in_time, indent=2))
except Exception as e:
logging.error(f"Failed to get VM points in time: {e}")
# Example 8: Get points in time stats for a VM
logging.info("\nExample 8: Getting points in time stats for VM")
try:
points_in_time_stats = client.vms.list_vm_points_in_time_stats(
vm_identifier=vm_id,
vpg_identifier=vpg_id # Optional, but may be required if VM is in multiple VPGs
)
logging.info("Points in time stats:")
logging.info(json.dumps(points_in_time_stats, indent=2))
except Exception as e:
logging.error(f"Failed to get VM points in time stats: {e}")
except Exception as e:
logging.error(f"Error occurred: {e}")
raise
if __name__ == "__main__":
main()
+138
View File
@@ -0,0 +1,138 @@
#!/usr/bin/python3
# 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.
"""
Zerto Volumes Example Script
This script demonstrates how to retrieve volume information from Zerto Virtual Manager (ZVM).
It shows how to list and filter volumes based on different criteria, making it useful for
volume management and monitoring.
Key Features:
1. List all volumes in the system
2. Filter volumes by:
- VPG association
- Datastore location
- Protected VM attachment
3. Display volume details:
- Volume identifiers
- Storage information
- Protection status
- Resource associations
Required Arguments:
--zvm_address: Site 1 ZVM address
client_id: Site 1 Keycloak client ID
client_secret: Site 1 Keycloak client secret
--ignore_ssl: Ignore SSL certificate verification (optional)
Example Usage:
python examples/volumes_example.py \
--zvm_address "192.168.1.100" \
client_id "zerto-api" \
client_secret "your-secret-here" \
--ignore_ssl
Note: This script focuses on volume operations and requires only Site 1 credentials
since it performs read-only operations on the protected site.
"""
import sys
import os
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import urllib3
import argparse
import logging
import json
from zvml import ZVMLClient
# Disable SSL warnings
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
def main():
parser = argparse.ArgumentParser(description="Zerto Volumes Example")
parser.add_argument("--zvm_address", required=True, help="ZVM address")
parser.add_argument('--client_id', required=True, help='Keycloak client ID')
parser.add_argument('--client_secret', required=True, help='Keycloak client secret')
parser.add_argument("--ignore_ssl", action="store_true", help="Ignore SSL certificate verification")
args = parser.parse_args()
# Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
try:
# Initialize the client
client = ZVMLClient(args.zvm_address, args.client_id, args.client_secret, not args.ignore_ssl)
# Example 1: List all volumes
logging.info("\nExample 1: Listing all volumes")
try:
volumes = client.volumes.list_volumes()
logging.info(f"Found {len(volumes)} volumes:")
logging.info(json.dumps(volumes, indent=2))
except Exception as e:
logging.error(f"Failed to list volumes: {e}")
# Example 2: List volumes for a specific VPG
logging.info("\nExample 2: Listing volumes for a specific VPG")
try:
# First get a VPG ID
vpgs = client.vpgs.list_vpgs()
# logging.info(f"vpgs: {json.dumps(vpgs, indent=2)}")
if vpgs:
vpg_id = vpgs[0]['VpgIdentifier']
logging.info(f"vpg_id: {vpg_id}")
volumes = client.volumes.list_volumes(vpg_identifier=vpg_id)
logging.info(f"Found {len(volumes)} volumes for VPG {vpg_id}:")
logging.info(json.dumps(volumes, indent=2))
except Exception as e:
logging.error(f"Failed to list volumes for VPG: {e}")
# Example 3: List volumes for a specific datastore
logging.info("\nExample 3: Listing volumes for a specific datastore")
try:
local_site1_identifier = client.localsite.get_local_site().get('SiteIdentifier')
logging.info(f"local_site1_identifier: {local_site1_identifier}")
# First get a datastore ID
datastores = client.virtualization_sites.get_virtualization_site_datastores(
site_identifier=local_site1_identifier
)
for datastore in datastores:
datastore_id = datastore['DatastoreIdentifier']
volumes = client.volumes.list_volumes(datastore_identifier=datastore_id)
logging.info(f"Found {len(volumes)} volumes for datastore {datastore_id}:")
logging.info(json.dumps(volumes, indent=2))
except Exception as e:
logging.error(f"Failed to list volumes for datastore: {e}")
# Example 4: List volumes for a specific protected VM
logging.info("\nExample 4: Listing volumes for a specific protected VM")
try:
# First get a VM ID
vms = client.vms.list_vms()
if vms:
vm_id = vms[0]['VmIdentifier']
volumes = client.volumes.list_volumes(protected_vm_identifier=vm_id)
logging.info(f"Found {len(volumes)} volumes for protected VM {vm_id}:")
logging.info(json.dumps(volumes, indent=2))
except Exception as e:
logging.error(f"Failed to list volumes for protected VM: {e}")
except Exception as e:
logging.error(f"Error occurred: {e}")
raise
if __name__ == "__main__":
main()
+422
View File
@@ -0,0 +1,422 @@
# 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.
"""
Zerto VPG Failover Example Script
This script demonstrates how to perform parallel failover tests for multiple Virtual Protection Groups (VPGs)
using the Zerto Virtual Manager (ZVM) API. It showcases complete VPG lifecycle management from creation
to testing and cleanup.
Key Features:
1. Site Management:
- Connect to protected site
- Retrieve local and peer site identifiers
- Manage cross-site replication using peer site information
2. VPG Operations:
- Create multiple VPGs with custom settings
- Add VMs to VPGs
- Monitor initial synchronization
- Create checkpoints
- Run parallel failover tests
- Generate test reports
- Clean up resources
3. Resource Management:
- Identify and select peer site datastores
- Configure peer site hosts and networks
- Set up peer site resource pools
- Manage VM folders
- Monitor volume replication
Required Arguments:
--zvm_address: Protected site ZVM address
--client_id: Protected site Keycloak client ID
--client_secret: Protected site Keycloak client secret
--ignore_ssl: Ignore SSL certificate verification (optional)
Example Usage:
python examples/vpg_failover_example.py \
--zvm_address "192.168.111.20" \
--client_id "zerto-api" \
--client_secret "your-secret-here" \
--ignore_ssl
Note: This script requires credentials only for the protected site. All recovery site information
is retrieved using the peer site API, eliminating the need for direct access to the recovery site.
Resource identifiers and configuration details for both sites are managed through the protected
site's ZVM API.
Script Flow:
1. Connects to protected site ZVM
2. Gets local and peer site information
3. Creates two VPGs in parallel:
- VpgTest1 protecting VM 'SmallCentOS'
- VpgTest2 protecting VM 'light-vm1'
4. Waits for both VPGs to reach MeetingSLA status
5. Performs parallel failover tests
6. Waits for user confirmation to stop tests
7. Stops failover tests in parallel
8. Generates test reports
9. Cleans up by deleting both VPGs
"""
import sys
import os
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import argparse
import logging
import urllib3
import json
from zvml import ZVMLClient
import time
from zvml.common import ZertoVPGStatus, ZertoVPGSubstatus
from zvml.recovery_reports import RecoveryReports
import threading
from concurrent.futures import ThreadPoolExecutor, as_completed
# Disable SSL warnings
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
#name sure the api client lifespan settion is complete the initial sync, in my case I set it to 3600 seconds
def setup_clients(args):
"""
Initialize and return Zerto clients and their local site identifiers for both sites
Args:
args: Parsed command line arguments
Returns:
tuple: (zvm_client, client2, local_site1_id, local_site2_id)
"""
# Create clients for both sites
zvm_client = ZVMLClient(
zvm_address=args.zvm_address,
client_id=args.client_id,
client_secret=args.client_secret,
verify_certificate=not args.ignore_ssl
)
#get all virtualization sites
virtualization_sites = zvm_client.virtualization_sites.get_virtualization_sites()
logging.info(f"Virtualization Sites: {json.dumps(virtualization_sites, indent=4)}")
# Get local site ids
local_site_identifier = zvm_client.localsite.get_local_site().get('SiteIdentifier')
logging.info(f"Site 1 Local Site ID: {local_site_identifier}")
#peer_site_identifier is site in the list that is not the local site
peer_site_identifier = next((site['SiteIdentifier'] for site in virtualization_sites if site['SiteIdentifier'] != local_site_identifier), None)
logging.info(f"Site 2 Local Site ID: {peer_site_identifier}")
return zvm_client, local_site_identifier, peer_site_identifier
def construct_vpg_settings(vpg_name, local_site_identifier, peer_site_identifier, site2_datastore_identifier,
site2_host_identifier, resource_pool_identifier, site2_folder_identifier, site2_network_identifier):
basic = {
"Name": vpg_name,
"VpgType": "Remote",
"RpoInSeconds": 300,
"JournalHistoryInHours": 1,
"Priority": "Medium",
"UseWanCompression": True,
"ProtectedSiteIdentifier": local_site_identifier,
"RecoverySiteIdentifier": peer_site_identifier
}
# Fill journal structure
journal = {
"DatastoreIdentifier": site2_datastore_identifier,
"Limitation": {
"HardLimitInMB": 153600,
"WarningThresholdInMB": 115200
}
}
# Fill recovery structure
recovery = {
"DefaultHostIdentifier": site2_host_identifier,
"DefaultDatastoreIdentifier": site2_datastore_identifier,
"DefaultResourcePoolIdentifier": resource_pool_identifier,
"DefaultFolderIdentifier": site2_folder_identifier
}
# Fill Networks structure
networks = {
"Failover": {
"Hypervisor": {
"DefaultNetworkIdentifier": site2_network_identifier
}
},
"FailoverTest": {
"Hypervisor": {
"DefaultNetworkIdentifier": site2_network_identifier
}
}
}
return basic, journal, recovery, networks
def perform_failover_test(client, vpg_name):
"""Execute failover test for a single VPG and get its report"""
try:
task_id = client.vpgs.failover_test(vpg_name)
logging.info(f"Failover test started for VPG {vpg_name}, task ID: {task_id}")
# Wait for task completion
client.tasks.wait_for_task_completion(task_id)
# Get the latest failover test report
latest_report = client.recovery_reports.get_latest_failover_test_report(vpg_name)
if latest_report:
logging.info(f"Failover test completed for VPG {vpg_name}")
return vpg_name, latest_report
return vpg_name, None
except Exception as e:
logging.error(f"Error in failover test for VPG {vpg_name}: {e}")
raise
def run_parallel_failover_tests(client, vpg_names):
"""Run failover tests in parallel for multiple VPGs"""
logging.info(f"Starting parallel failover tests for VPGs: {vpg_names}")
with ThreadPoolExecutor(max_workers=len(vpg_names)) as executor:
# Submit all failover tasks
future_to_vpg = {
executor.submit(perform_failover_test, client, vpg_name): vpg_name
for vpg_name in vpg_names
}
# Wait for all tasks to complete and collect results
results = {}
for future in as_completed(future_to_vpg):
vpg_name = future_to_vpg[future]
try:
vpg_name, report = future.result()
results[vpg_name] = report
logging.info(f"Completed failover test for VPG {vpg_name}")
except Exception as e:
logging.error(f"Failover test failed for VPG {vpg_name}: {e}")
results[vpg_name] = None
return results
def main():
parser = argparse.ArgumentParser(description="zvml Client")
parser.add_argument("--zvm_address", required=True, help="ZVM address")
parser.add_argument('--client_id', required=True, help='Keycloak client ID')
parser.add_argument('--client_secret', required=True, help='Keycloak client secret')
# parser.add_argument("--site2_address", required=True, help="Site 2 ZVM address")
# parser.add_argument('--site2_client_id', required=True, help='Site 2 Keycloak client ID')
# parser.add_argument('--site2_client_secret', required=True, help='Site 2 Keycloak client secret')
parser.add_argument("--ignore_ssl", action="store_true", help="Ignore SSL certificate verification")
args = parser.parse_args()
logging.basicConfig(level=logging.DEBUG)
try:
vpg_structure = [
{
'vpg_name': 'VpgTest1',
'vm_name': 'SmallCentOS'
},
{
'vpg_name': 'VpgTest2',
'vm_name': 'light-vm1'
}
]
# Setup clients and get site identifiers
zvm_client, local_site_identifier, peer_site_identifier = setup_clients(args)
# Get datastore identifier from site 2 using ZVM API
site2_datastores = zvm_client.virtualization_sites.get_virtualization_site_datastores(
site_identifier=peer_site_identifier
)
selected_datastore = next((ds for ds in site2_datastores if ds.get('DatastoreName') == "DS_VM_Right"), None)
site2_datastore_identifier = selected_datastore.get('DatastoreIdentifier')
logging.info(f"Site 2 Datastore ID: {site2_datastore_identifier}")
# Get host identifier from site 2 using ZVM API
site2_hosts = zvm_client.virtualization_sites.get_virtualization_site_hosts(
site_identifier=peer_site_identifier
)
# logging.info(f"Site 2 Hosts: {site2_hosts}")
# Get the first host from the list
selected_host = site2_hosts[0]
site2_host_identifier = selected_host.get('HostIdentifier')
# logging.info(f"Site 2 Host ID: {site2_host_identifier}")
# Get resource pools from site 2 using ZVM API
resource_pools = zvm_client.virtualization_sites.get_virtualization_site_resource_pools(
site_identifier=peer_site_identifier
)
if resource_pools:
resource_pool_identifier = resource_pools[0].get('ResourcePoolIdentifier')
logging.info(f"Resource Pool ID: {resource_pool_identifier}")
else:
logging.error("No resource pools found on site 2.")
return
# Get networks from site 2 using ZVM API
networks = zvm_client.virtualization_sites.get_virtualization_site_networks(
site_identifier=peer_site_identifier
)
if networks:
site2_network_identifier = networks[0].get('NetworkIdentifier')
logging.info(f"Network ID: {site2_network_identifier}")
else:
logging.error("No networks found on site 2.")
return
# Get folders from site 2 using ZVM API
folders = zvm_client.virtualization_sites.get_virtualization_site_folders(
site_identifier=peer_site_identifier
)
for folder in folders:
if folder.get('FolderName') == '/':
site2_folder_identifier = folder.get('FolderIdentifier')
logging.info(f"Folder ID: {site2_folder_identifier}")
break
# Get VMs from site 1 using ZVM API
site_1_vms = zvm_client.virtualization_sites.get_virtualization_site_vms(
site_identifier=local_site_identifier
)
logging.info(f"Found {len(site_1_vms)} VMs on site 1")
def create_vpg_with_vm(vpg_config):
"""Create a VPG and add a VM to it"""
try:
vpg_name = vpg_config['vpg_name']
vm_name = vpg_config['vm_name']
# Create VPG settings
basic, journal, recovery, networks = construct_vpg_settings(
vpg_name, local_site_identifier, peer_site_identifier,
site2_datastore_identifier, site2_host_identifier,
resource_pool_identifier, site2_folder_identifier, site2_network_identifier
)
# Create VPG
vpg_id = zvm_client.vpgs.create_vpg(
basic=basic, journal=journal, recovery=recovery, networks=networks, sync=True
)
logging.info(f"VPG {vpg_name} created successfully with ID: {vpg_id}")
# Find and add VM to VPG
vm = next((vm for vm in site_1_vms if vm.get('VmName') == vm_name), None)
if vm:
vm_payload = {
"VmIdentifier": vm.get('VmIdentifier'),
"Recovery": {
"HostIdentifier": site2_host_identifier,
"DatastoreIdentifier": site2_datastore_identifier,
"FolderIdentifier": site2_folder_identifier
}
}
task_id = zvm_client.vpgs.add_vm_to_vpg(vpg_name, vm_list_payload=vm_payload)
logging.info(f"Task ID: {task_id} to add VM {vm_name} to VPG {vpg_name}")
return vpg_name, vpg_id
else:
logging.error(f"VM {vm_name} not found")
return vpg_name, None
except Exception as e:
logging.error(f"Error creating VPG {vpg_name}: {e}")
return vpg_name, None
# Create VPGs in parallel
with ThreadPoolExecutor(max_workers=len(vpg_structure)) as executor:
# Submit all VPG creation tasks
future_to_vpg = {
executor.submit(create_vpg_with_vm, vpg_config): vpg_config
for vpg_config in vpg_structure
}
# Wait for all tasks to complete
created_vpgs = []
for future in as_completed(future_to_vpg):
vpg_name, vpg_id = future.result()
if vpg_id:
created_vpgs.append(vpg_name)
logging.info(f"Successfully created VPG {vpg_name}")
# Wait for all VPGs to reach MeetingSLA status
if created_vpgs:
logging.info("Waiting for VPGs to reach MeetingSLA status...")
while True:
all_vpgs_ready = True
for vpg_name in created_vpgs:
vpg_info = zvm_client.vpgs.list_vpgs(vpg_name=vpg_name)
status = vpg_info.get('Status')
substatus = vpg_info.get('SubStatus')
logging.info(f"VPG {vpg_name} - Status: {ZertoVPGStatus.get_name_by_value(status)}, "
f"SubStatus: {ZertoVPGSubstatus.get_name_by_value(substatus)}")
if not ((status == ZertoVPGStatus.MeetingSLA.value and
(substatus == ZertoVPGSubstatus.Sync.value or substatus == ZertoVPGSubstatus.NONE.value)) or
(status == ZertoVPGStatus.HistoryNotMeetingSLA.value)):
all_vpgs_ready = False
break
if all_vpgs_ready:
logging.info("All VPGs are now meeting SLA")
break
logging.info("Waiting for VPGs to reach MeetingSLA status...")
time.sleep(30) # Wait 30 seconds before checking again
input("Press Enter to start parallel failover tests for both VPGs...")
# Run parallel failover tests
failover_results = run_parallel_failover_tests(zvm_client, created_vpgs)
logging.info(f"Failover results: {json.dumps(failover_results, indent=4)}")
input("Press Enter to stop failover tests and rollback both VPGs...")
# Stop failover tests in parallel
with ThreadPoolExecutor(max_workers=len(created_vpgs)) as executor:
futures = [
executor.submit(zvm_client.vpgs.stop_failover_test, vpg_name)
for vpg_name in created_vpgs
]
for future in as_completed(futures):
try:
task_id = future.result()
logging.info(f"Failover test stop initiated, task ID: {task_id}")
except Exception as e:
logging.error(f"Error stopping failover test: {e}")
# After running failover tests
for vpg_name in created_vpgs:
# Get the latest failover test report
latest_report = zvm_client.recovery_reports.get_latest_failover_test_report(vpg_name)
if latest_report:
logging.info(f"Failover test report for {vpg_name} latest_report=:{json.dumps(latest_report, indent=4)}")
input("Press Enter to delete both VPGs...")
# Delete both VPGs
zvm_client.vpgs.delete_vpg(created_vpgs[0], force=True, keep_recovery_volumes=False)
logging.info(f"VPG {created_vpgs[0]} deleted successfully.")
zvm_client.vpgs.delete_vpg(created_vpgs[1], force=True, keep_recovery_volumes=False)
logging.info(f"VPG {created_vpgs[1]} deleted successfully.")
except Exception as e:
logging.exception("Error:")
logging.error(f"Error: {e}")
if __name__ == "__main__":
main()
+181
View File
@@ -0,0 +1,181 @@
# 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.
"""
Zerto VPG Settings Export/Import Example Script
This script demonstrates how to export and import Virtual Protection Group (VPG) settings
using the Zerto Virtual Manager (ZVM) API. It allows for backup and restoration of VPG
configurations, which is useful for disaster recovery planning and VPG replication.
Key Features:
1. VPG Settings Export:
- Export settings for specific VPGs or all VPGs
- Save exported settings to a JSON file
- Include all VPG configuration parameters
- Capture recovery site mappings
2. Settings Verification:
- List all available exported settings
- Read and display detailed settings
- Show summary of exported VPG configurations
- Verify export timestamp and status
3. VPG Settings Import:
- Import settings back to create new VPGs
- Restore original VPG configurations
- Support for multiple VPGs in single operation
- Validate import results
Required Arguments:
--zvm_address: Protected site ZVM address
--client_id: Protected site Keycloak client ID
--client_secret: Protected site Keycloak client secret
--ignore_ssl: Ignore SSL certificate verification (optional)
--vpg_names: Comma-separated list of VPG names to export (optional)
--output_file: File path to save exported settings (optional)
Example Usage:
python examples/vpg_setting_export_example.py \
--zvm_address "192.168.111.20" \
--client_id "zerto-api" \
--client_secret "your-secret-here" \
--vpg_names "VpgTest1,VpgTest2" \
--output_file "vpg_settings.json" \
--ignore_ssl
Script Flow:
1. Connects to protected site ZVM
2. Exports VPG settings:
- For specified VPGs if vpg_names provided
- For all VPGs if no vpg_names specified
3. Saves settings to file if output_file specified
4. Verifies export by reading settings
5. Displays VPG configuration summaries:
- VPG names
- Source and target sites
- RPO and journal history
6. Pauses for manual VPG deletion
7. Imports settings to recreate VPGs
8. Verifies import success
Note: This script requires only protected site credentials. It's designed for VPG
configuration backup and restore scenarios, allowing you to quickly recreate VPGs
with identical settings after changes or in disaster recovery situations.
"""
import argparse
import logging
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(levelname)s - %(message)s'
)
import urllib3
import json
import sys
import os
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from zvml import ZVMLClient
# Disable SSL warningss
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
def setup_client(args):
"""Initialize and return Zerto client"""
client = ZVMLClient(
zvm_address=args.zvm_address,
client_id=args.client_id,
client_secret=args.client_secret,
verify_certificate=not args.ignore_ssl
)
return client
def main():
parser = argparse.ArgumentParser(description="Export and Import VPG settings example")
parser.add_argument("--zvm_address", required=True, help="Site 1 ZVM address")
parser.add_argument('--client_id', required=True, help='Site 1 Keycloak client ID')
parser.add_argument('--client_secret', required=True, help='Site 1 Keycloak client secret')
parser.add_argument("--ignore_ssl", action="store_true", help="Ignore SSL certificate verification")
parser.add_argument("--vpg_names", help="Comma-separated list of VPG names to export settings for")
parser.add_argument("--output_file", help="Optional file to save the exported settings")
args = parser.parse_args()
try:
# Setup client
client = setup_client(args)
# If no VPG names provided, get all VPGs
if not args.vpg_names:
vpgs = client.vpgs.list_vpgs()
vpg_names = [vpg['VpgName'] for vpg in vpgs]
logging.info(f"No VPG names provided, exporting all {len(vpg_names)} VPGs")
else:
# Split the comma-separated string and strip whitespace
vpg_names = [name.strip() for name in args.vpg_names.split(',')]
logging.info(f"Exporting settings for VPGs: {vpg_names}")
# Step 1: Export VPG settings
print("\nStep 1: Exporting VPG settings...")
result = client.vpgs.export_vpg_settings(vpg_names)
print("Export Result:")
print(f"Timestamp: {result.get('TimeStamp')}")
print(f"Result: {result.get('ExportResult', {}).get('Result')}")
print(f"Message: {result.get('ExportResult', {}).get('Message')}")
# Save to file if specified
if args.output_file:
with open(args.output_file, 'w') as f:
json.dump(result, f, indent=2)
print(f"\nExported settings saved to: {args.output_file}")
# Step 2: Verify export and read settings
print("\nStep 2: Reading exported settings...")
exported_settings = client.vpgs.list_exported_vpg_settings()
export_timestamp = result.get('TimeStamp', '').split('.')[0] + '.000Z'
if any(setting.get('TimeStamp') == export_timestamp for setting in exported_settings):
print(f"Found export with timestamp {export_timestamp}")
settings = client.vpgs.read_exported_vpg_settings(export_timestamp)
# Display summary of exported VPG settings
vpg_settings = settings.get('ExportedVpgSettingsApi', [])
print(f"\nFound settings for {len(vpg_settings)} VPGs:")
for vpg in vpg_settings:
basic = vpg.get('Basic', {})
print(f"\nVPG Name: {basic.get('Name')}")
print(f"Source Site: {vpg.get('SourceSiteName')}")
print(f"Target Site: {vpg.get('TargetSiteName')}")
print(f"RPO (seconds): {basic.get('RpoInSeconds')}")
print(f"Journal History (hours): {basic.get('JournalHistoryInHours')}")
#pause
input("Delte VPG manually and Press Enter to continue...")
# Step 3: Import the settings back
print("\nStep 3: Importing VPG settings...")
import_result = client.vpgs.import_vpg_settings(settings)
print("\nImport Result:")
print(f"Result: {import_result.get('Result')}")
print(f"Message: {import_result.get('Message')}")
if import_result.get('VpgSettingsIds'):
print(f"Created VPG Settings IDs: {', '.join(import_result.get('VpgSettingsIds'))}")
#pause
input("Look at the VPG and verify whether the manual channges are reverted back to the original settings. Press Enter to continue...")
else:
print(f"\nWarning: Export with timestamp {export_timestamp} not found in exported settings list")
except Exception as e:
logging.exception("Error occurred:")
sys.exit(1)
if __name__ == "__main__":
main()
+304
View File
@@ -0,0 +1,304 @@
# 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.
"""
Zerto VPG VM Management Example Script
This script demonstrates how to manage Virtual Machines (VMs) within Virtual Protection Groups (VPGs)
using the Zerto Virtual Manager (ZVM) API. It showcases VPG creation, VM addition/removal, and cleanup.
Key Features:
1. Site Management:
- Connect to protected site
- Retrieve local and peer site identifiers
- Manage cross-site replication using peer site information
2. VPG Operations:
- Create multiple VPGs with custom settings
- Add multiple VMs to a VPG
- Remove VMs from VPGs
- Move VMs between VPGs
- Clean up resources
3. Resource Management:
- Identify and select peer site datastores, hosts, networks, folders and resource pools
Required Arguments:
--zvm_address: Protected site ZVM address
--client_id: Protected site Keycloak client ID
--client_secret: Protected site Keycloak client secret
--ignore_ssl: Ignore SSL certificate verification (optional)
Example Usage:
python examples/vpg_vms_example.py \
--zvm_address "192.168.111.20" \
--client_id "zerto-api" \
--client_secret "your-secret-here" \
--ignore_ssl
Note: This script requires credentials only for the protected site. All recovery site information
is retrieved using the peer site API, eliminating the need for direct access to the recovery site.
Script Flow:
1. Connects to protected site ZVM
2. Gets local and peer site information
3. Creates first VPG 'VpgTest1'
4. Adds two VMs (vm1 and vm2) to first VPG
5. Removes vm1 from first VPG
6. Creates second VPG 'VpgTest2'
7. Adds removed VM (vm1) to second VPG
8. Cleans up by deleting both VPGs
"""
import logging
# Configure logging before any other imports or code
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
import argparse
import urllib3
import json
import sys
import os
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from zvml import ZVMLClient
# Disable SSL warnings
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
def setup_clients(args):
"""
Initialize and return Zerto clients and their local site identifiers for both sites
Args:
args: Parsed command line arguments
Returns:
tuple: (client1, local_site1_id, local_peer_id)
"""
# Create clients for both sites
client = ZVMLClient(
zvm_address=args.zvm_address,
client_id=args.client_id,
client_secret=args.client_secret,
verify_certificate=not args.ignore_ssl
)
return client
def main():
parser = argparse.ArgumentParser(description="zvml Client")
parser.add_argument("--zvm_address", required=True, help="Site 1 ZVM address")
parser.add_argument('--client_id', required=True, help='Site 1 Keycloak client ID')
parser.add_argument('--client_secret', required=True, help='Site 1 Keycloak client secret')
parser.add_argument("--ignore_ssl", action="store_true", help="Ignore SSL certificate verification")
parser.add_argument("--vm1", required=True, help="Name of first VM to protect")
parser.add_argument("--vm2", required=True, help="Name of second VM to protect")
args = parser.parse_args()
try:
# Setup clients and get site identifiers
client1 = setup_clients(args)
virtualization_sites = client1.virtualization_sites.get_virtualization_sites()
logging.debug(f"Virtualization Sites: {json.dumps(virtualization_sites, indent=4)}")
# Get local site ids
local_site_identifier = client1.localsite.get_local_site().get('SiteIdentifier')
logging.info(f"Site 1 Local Site ID: {local_site_identifier}")
peer_site_identifier = next((site['SiteIdentifier'] for site in virtualization_sites if site['SiteIdentifier'] != local_site_identifier), None)
logging.info(f"Site 2 Local Site ID: {peer_site_identifier}")
# Get datastore identifier from site 2
peer_datastores = client1.virtualization_sites.get_virtualization_site_datastores(
site_identifier=peer_site_identifier
)
logging.debug(f"Site 2 Datastores: {json.dumps(peer_datastores, indent=4)}")
selected_datastore = next((ds for ds in peer_datastores if ds.get('DatastoreName') == "DS_VM_Right"), None)
peer_datastore_identifier = selected_datastore.get('DatastoreIdentifier')
logging.info(f"Site 2 Datastore ID: {peer_datastore_identifier}")
# Fill basic VPG settings info
vpg_name = 'VpgTest1'
basic = {
"Name": vpg_name,
"VpgType": "Remote",
"RpoInSeconds": 300,
"JournalHistoryInHours": 1,
"Priority": "Medium",
"UseWanCompression": True,
"ProtectedSiteIdentifier": local_site_identifier,
"RecoverySiteIdentifier": peer_site_identifier
}
# Fill journal structure
journal = {
"DatastoreIdentifier": peer_datastore_identifier,
"Limitation": {
"HardLimitInMB": 153600,
"WarningThresholdInMB": 115200
}
}
resource_pools = client1.virtualization_sites.get_virtualization_site_resource_pools(
site_identifier=peer_site_identifier
)
logging.debug(f"Resource Pools: {json.dumps(resource_pools, indent=4)}")
# Extract resource pool identifier from the first resource pool on the list
if resource_pools:
resource_pool_identifier = resource_pools[0].get('Identifier')
logging.info(f"Resource Pool Identifier from the first resource pool: {resource_pool_identifier}")
else:
logging.error("No resource pools found on site 2.")
return
# List networks from peer site
networks_list = client1.virtualization_sites.get_virtualization_site_networks(
site_identifier=peer_site_identifier
)
logging.debug(f"Networks: {json.dumps(networks_list, indent=4)}")
# Extract network identifier from the first network on the list
if networks_list:
network_identifier = networks_list[0].get('NetworkIdentifier')
logging.info(f"Network Identifier from the first network: {network_identifier}")
else:
logging.error("No networks found on site 2.")
return
#list folders from peer
folders = client1.virtualization_sites.get_virtualization_site_folders(
site_identifier=peer_site_identifier
)
logging.debug(f"Site 2 Folders: {json.dumps(folders, indent=4)}")
for folder in folders:
if folder.get('FolderName') == '/':
peer_folder_identifier = folder.get('FolderIdentifier')
logging.info(f"Folder Identifier: {peer_folder_identifier}")
break
peer_hosts = client1.virtualization_sites.get_virtualization_site_hosts(
site_identifier=peer_site_identifier
)
logging.debug(f"Site 2 Hosts: {json.dumps(peer_hosts, indent=4)}")
# get the second host from the list
peer_site_host_identifier = peer_hosts[1].get('HostIdentifier')
logging.info(f"Host Identifier from the second host: {peer_site_host_identifier}")
# Fill recovery structure
recovery = {
"DefaultHostIdentifier": peer_site_host_identifier,
"DefaultDatastoreIdentifier": peer_datastore_identifier,
"DefaultResourcePoolIdentifier": resource_pool_identifier,
"DefaultFolderIdentifier": peer_folder_identifier
}
# Fill Networks structure
networks = {
"Failover": {
"Hypervisor": {
"DefaultNetworkIdentifier": network_identifier
}
},
"FailoverTest": {
"Hypervisor": {
"DefaultNetworkIdentifier": network_identifier
}
}
}
input("Press Enter to create the first VPG...")
vpg_id = client1.vpgs.create_vpg(basic=basic, journal=journal,
recovery=recovery, networks=networks, sync=True)
logging.info(f"VPG ID: {vpg_id} created successfully.")
# Add VMs to the first VPG
vms = client1.virtualization_sites.get_virtualization_site_vms(
site_identifier=local_site_identifier
)
logging.debug(f"Site 1 VMs: {json.dumps(vms, indent=4)}")
vms_to_add = [args.vm1, args.vm2]
vm_list = []
for vm in vms:
logging.info(f"VM: Name={vm.get('VmName')}, VM Identifier={vm.get('VmIdentifier')}")
if vm.get('VmName') in vms_to_add:
logging.info(f"Adding VM {vm.get('VmName')} to VPG...")
vm_payload = {
"VmIdentifier": vm.get('VmIdentifier'),
"Recovery": {
"HostIdentifier": peer_site_host_identifier,
"DatastoreIdentifier": peer_datastore_identifier,
"FolderIdentifier": peer_folder_identifier
}
}
vm_list.append(vm_payload)
task_id = client1.vpgs.add_vm_to_vpg(vpg_name, vm_list_payload=vm_payload)
logging.info(f"Task ID: {task_id} to add VM {vm.get('VmName')} to VPG.")
input(f"Press Enter to remove {args.vm1} VM from the first VPG...")
# Remove first VM from the first VPG
vm_to_remove = args.vm1
vm_identifier_to_remove = None
for vm in vms:
if vm.get('VmName') == vm_to_remove:
vm_identifier_to_remove = vm.get('VmIdentifier')
task_id = client1.vpgs.remove_vm_from_vpg(vpg_name, vm_identifier_to_remove)
logging.info(f"Task ID: {task_id} to remove VM {vm_to_remove} from VPG {vpg_name}")
break
input("Press Enter to create second VPG...")
# Create second VPG
vpg_name_2 = 'VpgTest2'
basic['Name'] = vpg_name_2 # Update VPG name for second VPG
vpg_id_2 = client1.vpgs.create_vpg(basic=basic, journal=journal,
recovery=recovery, networks=networks, sync=True)
logging.info(f"Second VPG ID: {vpg_id_2} created successfully.")
# Add the removed VM to the second VPG
if vm_identifier_to_remove:
vm_payload = {
"VmIdentifier": vm_identifier_to_remove,
"Recovery": {
"HostIdentifier": peer_site_host_identifier,
"DatastoreIdentifier": peer_datastore_identifier,
"FolderIdentifier": peer_folder_identifier
}
}
task_id = client1.vpgs.add_vm_to_vpg(vpg_name_2, vm_list_payload=vm_payload)
logging.info(f"Task ID: {task_id} to add VM {vm_to_remove} to VPG {vpg_name_2}")
except Exception as e:
logging.exception("Error:")
logging.error(f"Error: {e}")
# wait for user input to continue
input("Press Enter to delete the VPGs...")
# Delete the VPGs
client1.vpgs.delete_vpg(vpg_name, force=True, keep_recovery_volumes=False)
logging.info(f"VPG {vpg_name} deleted successfully.")
client1.vpgs.delete_vpg(vpg_name_2, force=True, keep_recovery_volumes=False)
logging.info(f"VPG {vpg_name_2} deleted successfully.")
if __name__ == "__main__":
main()
+262
View File
@@ -0,0 +1,262 @@
# 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.
"""
Zerto Virtual Replication Appliance (VRA) Management Example Script
This script demonstrates how to manage VRAs using the Zerto Virtual Manager (ZVM) API.
It showcases VRA deployment, configuration, and cleanup operations.
Key Features:
1. VRA Management:
- List existing VRAs and their details
- Delete existing VRAs
- Deploy new VRAs with custom configurations
- Monitor VRA deployment status
2. Resource Selection:
- Interactive selection of hosts
- Interactive selection of datastores
- Interactive selection of networks
- Custom IP configuration for VRAs
3. Parallel Operations:
- Deploy multiple VRAs simultaneously
- Delete multiple VRAs in parallel
- Monitor multiple VRA operations
Required Arguments:
--zvm_address: ZVM server address
--client_id: Keycloak client ID
--client_secret: Keycloak client secret
--ignore_ssl: Ignore SSL certificate verification (optional)
Example Usage:
python examples/vras_example.py \
--zvm_address "192.168.111.20" \
--client_id "zerto-api" \
--client_secret "your-secret-here" \
--ignore_ssl
Script Flow:
1. Lists all existing VRAs and their details
2. Optionally deletes all existing VRAs
3. Creates new VRAs with user-selected resources:
- First VRA with IP 192.168.111.30
- Second VRA with IP 192.168.111.31 (optional)
4. Verifies final VRA configuration
Note: This script includes interactive prompts for resource selection and operation
confirmation. It demonstrates proper error handling and logging for VRA management
operations.
"""
#!/usr/bin/python3
import argparse
import logging
# logging.basicConfig(level=logging.DEBUG)
import urllib3
import json
import sys
import os
import time
from typing import Dict, List
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from zvml import ZVMLClient
# Disable SSL warnings
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
def setup_client(args):
"""Initialize and return Zerto client"""
client = ZVMLClient(
zvm_address=args.zvm_address,
client_id=args.client_id,
client_secret=args.client_secret,
verify_certificate=not args.ignore_ssl
)
return client
def get_user_confirmation(prompt: str) -> bool:
"""Get user confirmation for an action"""
while True:
response = input(f"\n{prompt} (yes/no): ").lower().strip()
if response in ['yes', 'y']:
return True
if response in ['no', 'n']:
return False
print("Please answer 'yes' or 'no'")
def select_from_list(items, item_type: str):
"""Let user select an item from a list"""
logging.info(f"select_from_list: {json.dumps(items, indent=2)}")
print(f"\nAvailable {item_type}s:")
for idx, item in enumerate(items, 1):
if 'VirtualizationHostName' in item: # For hosts
print(f"{idx}. {item['VirtualizationHostName']} ({item['HostIdentifier']})")
elif 'DatastoreName' in item: # For datastores
print(f"{idx}. {item['DatastoreName']} ({item['DatastoreIdentifier']})")
elif 'VirtualizationNetworkName' in item: # For networks
print(f"{idx}. {item['VirtualizationNetworkName']} ({item['NetworkIdentifier']})")
while True:
try:
choice = int(input(f"\nSelect {item_type} (1-{len(items)}): "))
if 1 <= choice <= len(items):
return items[choice - 1]
except ValueError:
pass
print(f"Please enter a number between 1 and {len(items)}")
def list_vras(client: ZVMLClient):
"""List all VRAs and their details"""
vras = client.vras.list_vras()
print(f"\nFound {len(vras)} VRAs:")
for vra in vras:
print(f"\nVRA Details:")
print(f" Name: {vra.get('VraName')}")
print(f" Status: {vra.get('Status')}")
print(f" IP Address: {vra.get('IpAddress')}")
print(f" Version: {vra.get('VraVersion')}")
print(f" Host: {vra.get('HostName')}")
def create_vra_with_selection(client: ZVMLClient, vra_number: int) -> Dict:
"""Create a VRA with user-selected resources"""
# Get local site information
local_site = client.localsite.get_local_site()
site_id = local_site['SiteIdentifier']
# Get available resources for the local site
print("\nRetrieving available resources...")
hosts = client.virtualization_sites.get_virtualization_site_hosts(site_id)
if not hosts:
raise ValueError("No hosts found in local site")
datastores = client.virtualization_sites.get_virtualization_site_datastores(site_id)
if not datastores:
raise ValueError("No datastores found in local site")
networks = client.virtualization_sites.get_virtualization_site_networks(site_id)
if not networks:
raise ValueError("No networks found in local site")
# Let user select resources
print("\nSelect resources for VRA deployment:")
host = select_from_list(hosts, "Host")
datastore = select_from_list(datastores, "Datastore")
network = select_from_list(networks, "Network")
while True:
# Let user customize IP address
default_ip = f"192.168.111.{30 + vra_number - 1}"
custom_ip = input(f"\nEnter VRA IP address (press Enter to use default {default_ip}): ").strip()
vra_ip = custom_ip if custom_ip else default_ip
# Create VRA configuration
vra_config = {
"hostIdentifier": host['HostIdentifier'],
"datastoreIdentifier": datastore['DatastoreIdentifier'],
"networkIdentifier": network['NetworkIdentifier'],
"hostRootPassword": input("\nEnter host root password: "),
"memoryInGb": 3,
"groupName": f"VRA_Group{vra_number}",
"vraNetworkDataApi": {
"vraIPConfigurationTypeApi": "Static",
"vraIPAddress": vra_ip,
"vraIPAddressRangeEnd": "",
"subnetMask": "255.255.255.0",
"defaultGateway": "192.168.111.254"
},
"usePublicKeyInsteadOfCredentials": False,
"populatePostInstallation": True,
"numOfCpus": 1,
"vmInstanceType": ""
}
# Create VRA
print(f"\nCreating VRA {vra_number} with configuration:")
print(json.dumps(vra_config, indent=2))
response = input("\nProceed with VRA creation? (yes/no/edit): ").lower().strip()
if response in ['yes', 'y']:
result = client.vras.create_vra(vra_config)
print(f"VRA creation initiated: {json.dumps(result, indent=2)}")
return result
elif response in ['no', 'n']:
return None
elif response in ['edit', 'e']:
print("\nRestarting VRA configuration...")
continue
else:
print("Please answer 'yes', 'no', or 'edit'")
def main():
parser = argparse.ArgumentParser(description="VRA Management Example")
parser.add_argument("--zvm_address", required=True, help="ZVM address")
parser.add_argument('--client_id', required=True, help='Keycloak client ID')
parser.add_argument('--client_secret', required=True, help='Keycloak client secret')
parser.add_argument("--ignore_ssl", action="store_true", help="Ignore SSL certificate verification")
args = parser.parse_args()
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
try:
# Setup client
client = setup_client(args)
# Step 1: List existing VRAs
print("\nStep 1: Listing existing VRAs...")
list_vras(client)
# Step 2: Ask for deletion confirmation
if get_user_confirmation("Would you like to delete all existing VRAs?"):
print("\nStep 2: Deleting existing VRAs...")
vras = client.vras.list_vras()
for vra in vras:
vra_id = vra.get('VraIdentifier')
print(f"Deleting VRA: {vra.get('VraName')} ({vra_id})")
client.vras.delete_vra(vra_id)
print("Waiting for deletion to complete...")
time.sleep(5) # Give some time for deletion to process
# Step 3: Create new VRAs
if get_user_confirmation("Would you like to create new VRAs?"):
print("\nStep 3: Creating new VRAs...")
# Create first VRA
print("\nConfiguring first VRA...")
result1 = create_vra_with_selection(client, 1)
if result1:
print("Waiting for first VRA deployment to complete...")
time.sleep(30)
# Create second VRA
if get_user_confirmation("Would you like to create a second VRA?"):
print("\nConfiguring second VRA...")
result2 = create_vra_with_selection(client, 2)
if result2:
print("Waiting for second VRA deployment to complete...")
time.sleep(30)
# Step 4: Verify final state
print("\nStep 4: Verifying final VRA configuration...")
list_vras(client)
except Exception as e:
logging.exception("Error occurred:")
sys.exit(1)
if __name__ == "__main__":
main()
+139
View File
@@ -0,0 +1,139 @@
# 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.
"""
Zerto Organizations (ZORG) Management Example Script
This script demonstrates how to manage Zerto Organizations (ZORGs) using the Zerto Virtual Manager (ZVM) API.
It showcases ZORG querying and information retrieval operations.
Key Features:
1. ZORG Management:
- List all ZORGs in the environment
- Query specific ZORG details by ID
- Retrieve detailed ZORG information
- Demonstrate ZORG filtering capabilities
2. Information Retrieval:
- Get ZORG identifiers
- Access ZORG configuration details
- View ZORG relationships and permissions
- Monitor ZORG status
3. Error Handling:
- Robust error handling for API requests
- Detailed logging of operations
- Graceful handling of missing ZORGs
Required Arguments:
--zvm_address: ZVM server address
--client_id: Keycloak client ID
--client_secret: Keycloak client secret
--ignore_ssl: Ignore SSL certificate verification (optional)
--zorg_id: Optional specific ZORG ID to query
Example Usage:
python examples/zorgs_example.py \
--zvm_address "192.168.111.20" \
--client_id "zerto-api" \
--client_secret "your-secret-here" \
--ignore_ssl \
--zorg_id "optional-zorg-id"
Script Flow:
1. Connects to ZVM server
2. Lists all available ZORGs
3. If specific ZORG ID provided:
- Retrieves detailed information for that ZORG
4. Otherwise:
- Gets details of first available ZORG
5. Outputs detailed ZORG information
Note: This script demonstrates basic ZORG management capabilities and can be used
as a foundation for more complex ZORG operations and automation.
"""
import sys
import os
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import argparse
import logging
import urllib3
import json
from zvml import ZVMLClient
# Disable SSL warnings
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
def main():
parser = argparse.ArgumentParser(description="Zerto Organizations (ZORG) Example")
parser.add_argument("--zvm_address", required=True, help="ZVM address")
parser.add_argument('--client_id', required=True, help='Keycloak client ID')
parser.add_argument('--client_secret', required=True, help='Keycloak client secret')
parser.add_argument("--ignore_ssl", action="store_true", help="Ignore SSL certificate verification")
parser.add_argument("--zorg_id", help="Optional: Specific ZORG ID to query")
args = parser.parse_args()
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
try:
# Connect to ZVM
logging.info(f"Connecting to ZVM at {args.zvm_address}")
client = ZVMLClient(
zvm_address=args.zvm_address,
client_id=args.client_id,
client_secret=args.client_secret,
verify_certificate=not args.ignore_ssl
)
# Test 1: Get all ZORGs
logging.info("\n=== Testing get_zorgs (all) ===")
try:
zorgs = client.zorgs.get_zorgs()
logging.info("All ZORGs:")
logging.info(json.dumps(zorgs, indent=2))
except Exception as e:
logging.error(f"Error getting all ZORGs: {e}")
# Test 2: Get specific ZORG if ID provided
if args.zorg_id:
logging.info(f"\n=== Testing get_zorgs with ID: {args.zorg_id} ===")
try:
zorg_details = client.zorgs.get_zorgs(args.zorg_id)
logging.info("ZORG details:")
logging.info(json.dumps(zorg_details, indent=2))
except Exception as e:
logging.error(f"Error getting ZORG {args.zorg_id}: {e}")
# Test 3: Get first ZORG details if any exist
elif zorgs and len(zorgs) > 0:
first_zorg = zorgs[0]
zorg_identifier = first_zorg.get('ZorgIdentifier')
if zorg_identifier:
logging.info(f"\n=== Testing get_zorgs with first found ID: {zorg_identifier} ===")
try:
zorg_details = client.zorgs.get_zorgs(zorg_identifier)
logging.info("First ZORG details:")
logging.info(json.dumps(zorg_details, indent=2))
except Exception as e:
logging.error(f"Error getting ZORG {zorg_identifier}: {e}")
except Exception as e:
logging.error(f"Error occurred: {e}")
raise
if __name__ == "__main__":
main()
+2
View File
@@ -0,0 +1,2 @@
requests>=2.31.0
urllib3>=2.1.0
+1
View File
@@ -0,0 +1 @@
from .client import ZVMLClient
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+324
View File
@@ -0,0 +1,324 @@
# 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
class Alerts:
def __init__(self, client):
self.client = client
# Manage ZVM Alerts
def get_alerts(self, start_date=None, end_date=None, vpg_name=None, zorg_identifier=None,
site_identifier=None, level=None, entity=None, help_identifier=None, is_dismissed=None,
alert_identifier=None):
"""
Fetches alerts from the Zerto API with optional filters or a specific alert if `alert_identifier` is provided.
:param start_date: The filter interval start date-time (string in date-time format).
:param end_date: The filter interval end date-time (string in date-time format).
:param vpg_name: The name of the VPG to filter alerts for.
:param zorg_identifier: The identifier of the ZORG.
:param site_identifier: The internal ZVM site identifier.
:param level: The alert level.
:param entity: The alert entity type.
:param help_identifier: The alert help identifier associated with the alert.
:param is_dismissed: True if alert was dismissed.
:param alert_identifier: The specific alert identifier to retrieve a single alert.
:return: List of alerts or a specific alert based on the provided filters.
"""
if alert_identifier:
alerts_uri = f"https://{self.client.zvm_address}/v1/alerts/{alert_identifier}"
else:
alerts_uri = f"https://{self.client.zvm_address}/v1/alerts"
logging.info(f'Alerts.get_alerts(alert_identifier={alert_identifier}, start_date={start_date}, end_date={end_date}, '
f'vpg_name={vpg_name}, zorg_identifier={zorg_identifier}, site_identifier={site_identifier}, '
f'level={level}, entity={entity}, help_identifier={help_identifier}, is_dismissed={is_dismissed})')
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
# Building query parameters for general alerts retrieval
params = {}
if not alert_identifier:
if start_date:
params['startDate'] = start_date
if end_date:
params['endDate'] = end_date
if vpg_name:
# Get VPG identifier from name
vpg = self.client.vpgs.get_vpg_by_name(vpg_name)
if vpg:
params['vpgIdentifier'] = vpg.get('VpgIdentifier')
logging.info(f"Found VPG identifier {params['vpgIdentifier']} for VPG name {vpg_name}")
else:
logging.warning(f"VPG with name {vpg_name} not found")
return []
if zorg_identifier:
params['zorgIdentifier'] = zorg_identifier
if site_identifier:
params['siteIdentifier'] = site_identifier
if level:
params['level'] = level
if entity:
params['entity'] = entity
if help_identifier:
params['helpIdentifier'] = help_identifier
if is_dismissed is not None:
params['isDismissed'] = str(is_dismissed).lower()
try:
logging.info("Fetching alerts...")
response = requests.get(alerts_uri, headers=headers, params=params, verify=self.client.verify_certificate)
response.raise_for_status()
alerts = response.json()
if not alerts:
logging.warning("No alerts found.")
return []
return alerts
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 dismiss_alert(self, alert_identifier):
"""
Dismisses a specific alert by its identifier.
:param alert_identifier: The identifier of the alert to be dismissed.
:return: Success message if the alert was dismissed, else an error message.
"""
logging.info(f'Alerts.dismiss_alert(alert_identifier={alert_identifier})')
# Construct the URL for dismissing the alert
dismiss_uri = f"https://{self.client.zvm_address}/v1/alerts/{alert_identifier}/dismiss"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
try:
logging.info(f"Attempting to dismiss alert with ID: {alert_identifier}")
response = requests.post(dismiss_uri, headers=headers, verify=self.client.verify_certificate)
response.raise_for_status()
if response.status_code == 200:
logging.info(f"Alert {alert_identifier} successfully dismissed.")
return f"Alert {alert_identifier} dismissed successfully."
else:
logging.warning(f"Unexpected response code: {response.status_code}")
return f"Alert {alert_identifier} dismissal returned an unexpected status."
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 undismiss_alert(self, alert_identifier):
"""
Undismisses a specific alert by its identifier.
:param alert_identifier: The identifier of the alert to be dismissed.
:return: Success message if the alert was dismissed, else an error message.
"""
logging.info(f'Alerts.undismiss_alert(alert_identifier={alert_identifier})')
# Construct the URL for dismissing the alert
undismiss_uri = f"https://{self.client.zvm_address}/v1/alerts/{alert_identifier}/undismiss"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
try:
logging.info(f"Attempting to undismiss alert with ID: {alert_identifier}")
response = requests.post(undismiss_uri, headers=headers, verify=self.client.verify_certificate)
response.raise_for_status()
if response.status_code == 200:
logging.info(f"Alert {alert_identifier} successfully undismissed.")
return f"Alert {alert_identifier} undismissed successfully."
else:
logging.warning(f"Unexpected response code: {response.status_code}")
return f"Alert {alert_identifier} undismissal returned an unexpected status."
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 get_alert_levels(self):
"""
Fetches the available alert levels from the Zerto API.
:return: List of alert levels or an error message if the request fails.
"""
logging.info('Alerts.get_alert_levels()')
# Construct the URL for fetching alert levels
alert_levels_uri = f"https://{self.client.zvm_address}/v1/alerts/levels"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
try:
logging.info("Fetching available alert levels...")
response = requests.get(alert_levels_uri, headers=headers, verify=self.client.verify_certificate)
response.raise_for_status()
alert_levels = response.json()
if not alert_levels:
logging.warning("No alert levels found.")
return []
return alert_levels
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 get_alert_entities(self):
"""
Fetches the available alert entities from the Zerto API.
:return: List of alert entities or an error message if the request fails.
"""
logging.info('Alerts.get_alert_entities()')
# Construct the URL for fetching alert entities
alert_entities_uri = f"https://{self.client.zvm_address}/v1/alerts/entities"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
try:
logging.info("Fetching available alert entities...")
response = requests.get(alert_entities_uri, headers=headers, verify=self.client.verify_certificate)
response.raise_for_status()
alert_entities = response.json()
if not alert_entities:
logging.warning("No alert entities found.")
return []
return alert_entities
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 get_alert_help_identifiers(self):
"""
Fetches the available alert help identifiers from the Zerto API.
:return: List of alert help identifiers or an error message if the request fails.
"""
logging.info('Alerts.get_alert_help_identifiers()')
# Construct the URL for fetching alert help identifiers
help_identifiers_uri = f"https://{self.client.zvm_address}/v1/alerts/helpidentifiers"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
try:
logging.info("Fetching available alert help identifiers...")
response = requests.get(help_identifiers_uri, headers=headers, verify=self.client.verify_certificate)
response.raise_for_status()
help_identifiers = response.json()
if not help_identifiers:
logging.warning("No alert help identifiers found.")
return []
return help_identifiers
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
+109
View File
@@ -0,0 +1,109 @@
# 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 ssl
# Configure logging with timestamp format
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
# Import all necessary classes
from .tasks import Tasks
from .vpgs import VPGs
from .vms import VMs
from .failover import Failover
from .alerts import Alerts
from .peersites import PeerSites
from .events import Events
from .repositories import Repositories
from .sessions import Sessions
from .recoveryscripts import RecoveryScripts
from .encryptiondetection import EncryptionDetection
from .zorgs import Zorgs
from .localsite import LocalSite
from .datastores import Datastores
from .vras import VRA
from .recovery_reports import RecoveryReports
from .license import License
from .service_profiles import ServiceProfiles
from .server_date_time import ServerDateTime
from .virtualization_sites import VirtualizationSites
from .volumes import Volumes
from .tweaks import Tweaks
# Disable SSL warnings for self-signed certificates
context = ssl._create_unverified_context()
class ZVMLClient:
def __init__(self, zvm_address, client_id, client_secret, verify_certificate=True):
self.zvm_address = zvm_address
self.client_id = client_id
self.client_secret = client_secret
self.verify_certificate = verify_certificate
self.token = None
self.token_expiry = None
self.__get_keycloak_token()
self.tasks = Tasks(self)
self.vpgs = VPGs(self)
self.vms = VMs(self)
self.failover = Failover(self)
self.alerts = Alerts(self)
self.peersites = PeerSites(self)
self.events = Events(self)
self.repositories = Repositories(self)
self.sessions = Sessions(self)
self.recoveryscripts = RecoveryScripts(self)
self.zorgs = Zorgs(self)
self.encryptiondetection = EncryptionDetection(self)
self.localsite = LocalSite(self.zvm_address, self.token)
self.datastores = Datastores(self)
self.vras = VRA(self)
self.recovery_reports = RecoveryReports(self)
self.license = License(self)
self.service_profiles = ServiceProfiles(self)
self.server_date_time = ServerDateTime(self)
self.virtualization_sites = VirtualizationSites(self)
self.volumes = Volumes(self)
self.tweaks = Tweaks(self)
def __get_keycloak_token(self):
logging.debug(f'__get_keycloak_token(zvm_address={self.zvm_address})')
keycloak_uri = f"https://{self.zvm_address}/auth/realms/zerto/protocol/openid-connect/token"
headers = {'Content-Type': 'application/x-www-form-urlencoded'}
body = {
'client_id': self.client_id,
'client_secret': self.client_secret,
'grant_type': 'client_credentials',
'expires_in': 3600 # Request token expiration in seconds (e.g., 1 hour)
}
try:
logging.info("Connecting to Keycloak to get token...")
response = requests.post(keycloak_uri, headers=headers, data=body, verify=self.verify_certificate)
response.raise_for_status()
token_data = response.json()
self.token = token_data.get('access_token')
self.token_expiry = token_data.get('expires_in') # Store expiration time
logging.info(f"Successfully retrieved token.")
logging.info(f"Token expiration details:")
logging.info(f"- Expires in: {self.token_expiry} seconds")
logging.info(f"- Requested expiration: {body['expires_in']} seconds")
if self.token_expiry != body['expires_in']:
logging.warning(f"Server provided different expiration time than requested!")
return self.token
except requests.exceptions.RequestException as e:
logging.error(f"Error retrieving token: {e}")
raise
+599
View File
@@ -0,0 +1,599 @@
# 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.
from enum import Enum
class ZertoTaskTypes(Enum):
CreateProtectionGroup = 0
RemoveProtectionGroup = 1
FailOver = 2
FailOverTest = 3
StopFailOverTest = 4
Move = 5
GetCheckpointList = 6
ProtectVM = 7
UnprotectVM = 8
AddVMToProtectionGroup = 9
RemoveVMFromProtectionGroup = 10
InstallVra = 11
UninstallVra = 12
GetVMSettings = 13
UpdateProtectionGroup = 14
InsertTaggedCP = 15
WaitForCP = 16
HandleMirrorPromotion = 17
ActivateAllMirrors = 18
LogCollection = 19
ClearCheckpoints = 20
ForceReconfigurationOfNewVM = 21
ClearSite = 22
ForceRemoveProtectionGroup = 23
ForceUpdateProtectionGroup = 24
ForceKillProtectionGroup = 25
PrePostScript = 26
InitFullSync = 27
Pair = 28
Unpair = 29
AddPeerVraInfo = 30
RemovePeerVraInfo = 31
InstallCloudConnector = 32
UninstallCloudConnector = 33
HandleFirstSyncDone = 34
Clone = 35
MoveBeforeCommit = 36
MoveRollback = 37
MoveCommit = 38
UpgradeVRA = 39
MaintainHost = 40
NotSupportedInThisVersion = 41
MoveProtectionGroupToManualOperationNeeded = 42
FailoverBeforeCommit = 43
FailoverCommit = 44
@classmethod
def get_name_by_value(cls, value):
for member in cls:
if member.value == value:
return member.name
return None
@classmethod
def get_value_by_name(cls, name):
for member in cls:
if member.name == name:
return member.value
return None
class ZertoTaskStates(Enum):
FirstUnusedValue = 0
InProgress = 1
WaitingForUserInput = 2
Paused = 3
Failed = 4
Stopped = 5
Completed = 6
Cancelling = 7
@classmethod
def get_name_by_value(cls, value):
for member in cls:
if member.value == value:
return member.name
return None
@classmethod
def get_value_by_name(cls, name):
for member in cls:
if member.name == name:
return member.value
return None
class ZertoVPGStatus(Enum):
Initializing = 0
MeetingSLA = 1
NotMeetingSLA = 2
HistoryNotMeetingSLA = 3
RpoNotMeetingSLA = 4
FailingOver = 5
Moving = 6
Deleting = 7
Recovered = 8
@classmethod
def get_name_by_value(cls, value):
for member in cls:
if member.value == value:
return member.name
return None
@classmethod
def get_value_by_name(cls, name):
for member in cls:
if member.name == name:
return member.value
return None
class ZertoVPGSubstatus(Enum):
NONE = 0 # Using NONE instead of None as None is a Python keyword
InitialSync = 1
Creating = 2
VolumeInitialSync = 3
Sync = 4
RecoveryPossible = 5
DeltaSync = 6
NeedsConfiguration = 7
Error = 8
EmptyProtectionGroup = 9
DisconnectedFromPeerNoRecoveryPoints = 10
FullSync = 11
VolumeDeltaSync = 12
VolumeFullSync = 13
FailingOverCommitting = 14
FailingOverBeforeCommit = 15
FailingOverRollingBack = 16
Promoting = 17
MovingCommitting = 18
MovingBeforeCommit = 19
MovingRollingBack = 20
Deleting = 21
PendingRemove = 22
BitmapSync = 23
DisconnectedFromPeer = 24
ReplicationPausedUserInitiated = 25
ReplicationPausedSystemInitiated = 26
RecoveryStorageProfileError = 27
Backup = 28
RollingBack = 29
RecoveryStorageError = 30
JournalStorageError = 31
VmNotProtectedError = 32
@classmethod
def get_name_by_value(cls, value):
for member in cls:
if member.value == value:
return member.name
return None
@classmethod
def get_value_by_name(cls, name):
for member in cls:
if member.name == name:
return member.value
return None
class ZertoProtectedSiteType(Enum):
VCVpg = 0
VCvApp = 1
VCDvApp = 2
AWS = 3
HyperV = 4
@classmethod
def get_name_by_value(cls, value):
for member in cls:
if member.value == value:
return member.name
return None
@classmethod
def get_value_by_name(cls, name):
for member in cls:
if member.name == name:
return member.value
return None
class ZertoRecoverySiteType(Enum):
VCVpg = 0
VCvApp = 1
VCDvApp = 2
AWS = 3
HyperV = 4
@classmethod
def get_name_by_value(cls, value):
for member in cls:
if member.value == value:
return member.name
return None
@classmethod
def get_value_by_name(cls, name):
for member in cls:
if member.name == name:
return member.value
return None
class ZertoVPGPriority(Enum):
Low = 0
Medium = 1
High = 2
@classmethod
def get_name_by_value(cls, value):
for member in cls:
if member.value == value:
return member.name
return None
class ZertoVRAStatus(Enum):
Installed = 0
UnsupportedEsxVersion = 1
NotInstalled = 2
Installing = 3
Removing = 4
InstallationError = 5
HostPasswordChanged = 6
UpdatingIpSettings = 7
DuringChangeHost = 8
@classmethod
def get_name_by_value(cls, value):
for member in cls:
if member.value == value:
return member.name
return None
class ZertoPairingStatus(Enum):
Paired = 0
Pairing = 1
Unpaired = 2
@classmethod
def get_name_by_value(cls, value):
for member in cls:
if member.value == value:
return member.name
return None
class ZertoAlertLevel(Enum):
Warning = 0
Error = 1
@classmethod
def get_name_by_value(cls, value):
for member in cls:
if member.value == value:
return member.name
return None
class ZertoAlertEntity(Enum):
Zvm = 0
Vra = 1
Vpg = 2
CloudConnector = 3
Storage = 4
License = 5
Zcm = 6
FileRecoveryComponent = 7
@classmethod
def get_name_by_value(cls, value):
for member in cls:
if member.value == value:
return member.name
return None
class ZertoAlertHelpIdentifier(Enum):
AWS0001 = 0
BCK0001 = 1
BCK0002 = 2
BCK0005 = 3
BCK0006 = 4
BCK0007 = 5
LIC0001 = 6
LIC0002 = 7
LIC0003 = 8
LIC0004 = 9
LIC0005 = 10
LIC0006 = 11
LIC0007 = 12
LIC0008 = 13
STR0001 = 14
STR0002 = 15
STR0004 = 16
VCD0001 = 17
VCD0002 = 18
VCD0003 = 19
VCD0004 = 20
VCD0005 = 21
VCD0006 = 22
VCD0007 = 23
VCD0010 = 24
VCD0014 = 25
VCD0015 = 26
VCD0016 = 27
VCD0017 = 28
VCD0018 = 29
VCD0020 = 30
VCD0021 = 31
VPG0003 = 32
VPG0004 = 33
VPG0005 = 34
VPG0006 = 35
VPG0007 = 36
VPG0008 = 37
VPG0009 = 38
VPG0010 = 39
VPG0011 = 40
VPG0012 = 41
VPG0014 = 42
VPG0015 = 43
VPG0016 = 44
VPG0017 = 45
VPG0018 = 46
VPG0019 = 47
VPG0020 = 48
VPG0021 = 49
VPG0022 = 50
VPG0023 = 51
VPG0024 = 52
VPG0025 = 53
VPG0026 = 54
VPG0027 = 55
VPG0028 = 56
VPG0035 = 57
VPG0036 = 58
VPG0037 = 59
VPG0038 = 60
VPG0039 = 61
VPG0040 = 62
VPG0041 = 63
VPG0042 = 64
VPG0043 = 65
VPG0044 = 66
VPG0045 = 67
VPG0046 = 68
VPG0047 = 69
VPG0048 = 70
VRA0001 = 71
VRA0002 = 72
VRA0003 = 73
VRA0004 = 74
VRA0005 = 75
VRA0006 = 76
VRA0007 = 77
VRA0008 = 78
VRA0009 = 79
VRA0010 = 80
VRA0011 = 81
VRA0012 = 82
VRA0013 = 83
VRA0014 = 84
VRA0015 = 85
VRA0016 = 86
VRA0017 = 87
VRA0018 = 88
VRA0019 = 89
VRA0020 = 90
VRA0021 = 91
VRA0022 = 92
VRA0023 = 93
VRA0024 = 94
VRA0025 = 95
VRA0026 = 96
VRA0027 = 97
VRA0028 = 98
VRA0029 = 99
VRA0030 = 100
VRA0032 = 101
VRA0035 = 102
VRA0036 = 103
VRA0037 = 104
VRA0038 = 105
VRA0039 = 106
VRA0040 = 107
VRA0049 = 108
VRA0050 = 109
VRA0051 = 110
VRA0052 = 111
VRA0053 = 112
VRA0054 = 113
VRA0055 = 114
ZCC0001 = 115
ZCC0002 = 116
ZCC0003 = 117
ZCM0001 = 118
ZVM0001 = 119
ZVM0002 = 120
ZVM0003 = 121
ZVM0004 = 122
ZVM0005 = 123
ZVM0006 = 124
ZVM0007 = 125
ZVM0008 = 126
ZVM0009 = 127
ZVM0010 = 128
ZVM0011 = 129
ZVM0012 = 130
ZVM0013 = 131
ZVM0014 = 132
ZVM0015 = 133
FLR0001 = 134
Unknown = 135
@classmethod
def get_name_by_value(cls, value):
for member in cls:
if member.value == value:
return member.name
return None
class ZertoEventType(Enum):
Unknown = 0
CreateProtectionGroup = 1
RemoveProtectionGroup = 2
FailOver = 3
FailOverTest = 4
StopFailOverTest = 5
Move = 6
ProtectVM = 7
UnprotectVM = 8
InstallVra = 9
UninstallVra = 10
UpdateProtectionGroup = 11
InsertTaggedCP = 12
HandleMirrorPromotion = 13
ActivateAllMirrors = 14
LogCollection = 15
ForceReconfigurationOfNewVM = 16
ClearSite = 17
ForceRemoveProtectionGroup = 18
ForceUpdateProtectionGroup = 19
ForceKillProtectionGroup = 20
PrePostScript = 21
InitFullSync = 22
Pair = 23
Unpair = 24
InstallCloudConnector = 25
UninstallCloudConnector = 26
RedeployCloudConnector = 27
ScriptExecutionFailure = 28
SetAdvancedSiteSettings = 29
Clone = 30
KeepDisk = 31
FailoverBeforeCommit = 32
FailoverCommit = 33
FailoverRollback = 34
MoveBeforeCommit = 35
MoveRollback = 36
MoveCommit = 37
MaintainHost = 38
UpgradeVra = 39
MoveProtectionGroupToManualOperationNeeded = 40
ChangeVraIpSettings = 41
PauseProtectionGroup = 42
ResumeProtectionGroup = 43
UpgradeZVM = 44
BulkUpgradeVras = 45
BulkUninstallVras = 46
AlertTurnedOn = 47
AlertTurnedOff = 48
ChangeVraPassword = 49
ChangeRecoveryHost = 50
BackupProtectionGroup = 51
CleanupProtectionGroupVipDiskbox = 52
RestoreProtectionGroup = 53
PreScript = 54
PostScript = 55
RemoveVmFromVc = 56
ChangeVraPasswordIpSettings = 57
FlrJournalMount = 58
FlrJournalUnmount = 59
Login = 60
@classmethod
def get_name_by_value(cls, value):
for member in cls:
if member.value == value:
return member.name
return None
class ZertoEventCategory(Enum):
All = 0
Events = 1
Alerts = 2
@classmethod
def get_name_by_value(cls, value):
for member in cls:
if member.value == value:
return member.name
return None
class ZertoCommitPolicy(Enum):
Rollback = 0
Commit = 1
NONE = 2 # Using NONE instead of None as None is a Python keyword
@classmethod
def get_name_by_value(cls, value):
for member in cls:
if member.value == value:
return member.name
return None
class ZertoShutdownPolicy(Enum):
NONE = 0 # Using NONE instead of None as None is a Python keyword
Shutdown = 1
ForceShutdown = 2
@classmethod
def get_name_by_value(cls, value):
for member in cls:
if member.value == value:
return member.name
return None
class ZertoVRAIPConfigType(Enum):
Dhcp = 0
Static = 1
@classmethod
def get_name_by_value(cls, value):
for member in cls:
if member.value == value:
return member.name
return None
class ZertoVPGSettingsBackupRetentionPeriod(Enum):
OneWeek = 0
OneMonth = 1
ThreeMonths = 2
SixMonths = 3
NineMonths = 4
OneYear = 5
@classmethod
def get_name_by_value(cls, value):
for member in cls:
if member.value == value:
return member.name
return None
class ZertoVPGSettingsBackupSchedulerDOW(Enum):
Sunday = 0
Monday = 1
Tuesday = 2
Wednesday = 3
Thursday = 4
Friday = 5
Saturday = 6
@classmethod
def get_name_by_value(cls, value):
for member in cls:
if member.value == value:
return member.name
return None
class ZertoVPGSettingsBackupSchedulerPeriod(Enum):
Daily = 0
Weekly = 1
@classmethod
def get_name_by_value(cls, value):
for member in cls:
if member.value == value:
return member.name
return None
class ZertoTweakType(Enum):
ZVM = "zvm-tweak"
VRA = "vra-tweak"
Frontend = "frontend-tweak"
@classmethod
def get_name_by_value(cls, value):
for member in cls:
if member.value == value:
return member.name
return None
+45
View File
@@ -0,0 +1,45 @@
# 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
class Datastores:
def __init__(self, client):
self.client = client
def list_datastores(self, datastore_identifier=None):
if datastore_identifier:
logging.info(f"Datastores.list_datastores: Fetching datastore information for identifier: {datastore_identifier}...")
url = f"https://{self.client.zvm_address}/v1/datastores/{datastore_identifier}"
else:
logging.info("Datastores.list_datastores: Fetching all datastores information...")
url = f"https://{self.client.zvm_address}/v1/datastores"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
try:
response = requests.get(url, headers=headers, verify=self.client.verify_certificate)
response.raise_for_status()
if datastore_identifier:
logging.info(f"Datastores.list_datastores: Successfully retrieved datastore information for identifier: {datastore_identifier}.")
else:
logging.info("Datastores.list_datastores: Successfully retrieved all datastores information.")
return response.json()
except requests.exceptions.RequestException as e:
if datastore_identifier:
logging.error(f"Datastores.list_datastores: Failed to get datastore information for identifier {datastore_identifier}: {e}")
else:
logging.error(f"Datastores.list_datastores: Failed to get all datastores information: {e}")
raise
+124
View File
@@ -0,0 +1,124 @@
# 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
from typing import List, Dict
class EncryptionDetection:
def __init__(self, client):
self.client = client
def get_encryption_detections(self):
url = f"https://{self.client.zvm_address}/v1/encryptiondetection"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
logging.info(f"EncryptionDetection.get_encryption_detections(zvm_address={self.client.zvm_address})")
try:
response = requests.get(url, headers=headers, 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 get_encryption_detection(self, detection_identifier):
url = f"https://{self.client.zvm_address}/v1/encryptiondetection/{detection_identifier}"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
logging.info(f"EncryptionDetection.get_encryption_detection(zvm_address={self.client.zvm_address}, detection_identifier={detection_identifier})")
try:
response = requests.get(url, headers=headers, 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 get_encryption_detection_types(self):
url = f"https://{self.client.zvm_address}/v1/encryptiondetection/types"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
logging.info(f"EncryptionDetection.get_encryption_detection_types(zvm_address={self.client.zvm_address})")
try:
response = requests.get(url, headers=headers, 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_suspected_volumes(self) -> List[Dict]:
"""List all suspected encrypted volumes.
Returns:
List[Dict]: List of suspected encrypted volumes with their details
Raises:
requests.exceptions.RequestException: If the API request fails
"""
logging.info(f"EncryptionDetection.list_suspected_volumes(zvm_address={self.client.zvm_address})")
url = f"https://{self.client.zvm_address}/v1/encryptiondetection/suspected/volumes"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
try:
response = requests.get(url, headers=headers, verify=self.client.verify_certificate)
response.raise_for_status()
result = response.json()
logging.info(f"Successfully retrieved {len(result)} suspected encrypted volumes")
logging.debug(f"EncryptionDetection.list_suspected_volumes result: {json.dumps(result, indent=4)}")
return result
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
+241
View File
@@ -0,0 +1,241 @@
# 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
class Events:
def __init__(self, client):
self.client = client
def list_events(self, event_identifier=None, start_date=None, end_date=None, vpg_identifier=None,
site_name=None, site_identifier=None, zorg_identifier=None, event_type=None,
entity_type=None, category=None, user_name=None, alert_identifier=None):
"""
Fetches a list of events or a specific event from the Zerto API with optional filters.
:param event_identifier: The identifier of the specific event (if fetching a specific event).
:param start_date: The filter interval start date-time (string in date-time format).
:param end_date: The filter interval end date-time (string in date-time format).
:param vpg_identifier: The identifier of the VPG.
:param site_name: The name of the site.
:param site_identifier: The internal ZVM site identifier.
:param zorg_identifier: The identifier of the ZORG.
:param event_type: The event type.
:param entity_type: The entity type to return.
:param category: The event category to return.
:param user_name: The username for which the event occurred.
:param alert_identifier: The alert identifier.
:return: List of events or a specific event based on provided filters.
"""
logging.info(f'Events.list_events(event_identifier={event_identifier}, start_date={start_date}, end_date={end_date}, '
f'vpg_identifier={vpg_identifier}, site_name={site_name}, site_identifier={site_identifier}, '
f'zorg_identifier={zorg_identifier}, event_type={event_type}, entity_type={entity_type}, '
f'category={category}, user_name={user_name}, alert_identifier={alert_identifier})')
# Determine endpoint based on whether event_identifier is provided
if event_identifier:
events_uri = f"https://{self.client.zvm_address}/v1/events/{event_identifier}"
else:
events_uri = f"https://{self.client.zvm_address}/v1/events"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
# Building query parameters
params = {}
if start_date:
params['startDate'] = start_date
if end_date:
params['endDate'] = end_date
if vpg_identifier:
params['vpgIdentifier'] = vpg_identifier
if site_name:
params['siteName'] = site_name
if site_identifier:
params['siteIdentifier'] = site_identifier
if zorg_identifier:
params['zorgIdentifier'] = zorg_identifier
if event_type:
params['eventType'] = event_type
if entity_type:
params['entityType'] = entity_type
if category:
params['category'] = category
if user_name:
params['userName'] = user_name
if alert_identifier:
params['alertIdentifier'] = alert_identifier
try:
logging.info("Fetching events with specified filters...")
response = requests.get(events_uri, headers=headers, params=params, verify=self.client.verify_certificate)
response.raise_for_status()
events = response.json()
if not events:
logging.warning("No events found.")
return []
return events
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 list_event_types(self):
"""
Fetches a list of event types from the Zerto API.
:return: List of event types.
"""
logging.info(f'Events.list_event_types(zvm_address={self.client.zvm_address})')
event_types_uri = f"https://{self.client.zvm_address}/v1/events/types"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
try:
logging.info("Fetching event types...")
response = requests.get(event_types_uri, headers=headers, verify=self.client.verify_certificate)
response.raise_for_status()
event_types = response.json()
if not event_types:
logging.warning("No event types found.")
return []
return event_types
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 list_event_entities(self):
"""
Fetches a list of event entities from the Zerto API.
:return: List of event entities.
"""
logging.info(f'Events.list_event_entities(zvm_address={self.client.zvm_address})')
event_entities_uri = f"https://{self.client.zvm_address}/v1/events/entities"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
try:
logging.info("Fetching event entities...")
response = requests.get(event_entities_uri, headers=headers, verify=self.client.verify_certificate)
response.raise_for_status()
event_entities = response.json()
if not event_entities:
logging.warning("No event entities found.")
return []
return event_entities
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 list_event_categories(self):
"""
Fetches a list of event categories from the Zerto API.
:return: List of event categories.
"""
logging.info(f'Events.list_event_categories(zvm_address={self.client.zvm_address})')
event_categories_uri = f"https://{self.client.zvm_address}/v1/events/categories"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
try:
response = requests.get(event_categories_uri, headers=headers, verify=self.client.verify_certificate)
response.raise_for_status()
event_categories = response.json()
if not event_categories:
logging.warning("No event categories found.")
return []
return event_categories
except requests.exceptions.RequestException as e:
logging.error(f"Error fetching event categories: {e}")
return None
url = f"https://{self.client.zvm_address}/v1/events/types"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
try:
response = requests.get(url, headers=headers, 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
except Exception as e:
logging.error(f"Unexpected error: {e}")
raise
+21
View File
@@ -0,0 +1,21 @@
# 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
class Failover:
def __init__(self, client):
self.client = client
def failover(self, vpg_name, checkpoint_identifier=None, vm_name_list=None, commit_policy=0, time_to_wait_before_shutdown_sec=3600, shutdown_policy=0, is_reverse_protection=False, sync=None):
# Implementation of failover method
pass
+164
View File
@@ -0,0 +1,164 @@
# 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
class License:
def __init__(self, client):
self.client = client
def get_license(self):
"""
Fetch license information from the Zerto server.
Returns:
dict: The license information from the Zerto server, or an empty dictionary if no content is returned.
"""
logging.info(f'License.get_license(zvm_address={self.client.zvm_address})')
url = f"https://{self.client.zvm_address}/v1/license"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
try:
logging.info("Fetching license information...")
response = requests.get(url, headers=headers, verify=self.client.verify_certificate)
# Handle 204 No Content
if response.status_code == 204:
logging.info("No license information available.")
return {}
# Raise an error for other non-successful HTTP status codes
response.raise_for_status()
# Parse the response JSON
license_info = response.json()
logging.info("Successfully fetched license information.")
return license_info
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 details: {json.dumps(error_details, indent=2)}")
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 while fetching license information: {e}")
raise
def put_license(self, license_key):
"""
Add a new license or update an existing one on the Zerto server.
Args:
license_key (str): The license key to add or update.
Returns:
dict: The response from the Zerto server, or an empty dictionary if no content is returned.
"""
logging.info(f'License.put_license(zvm_address={self.client.zvm_address}, license_key={license_key})')
url = f"https://{self.client.zvm_address}/v1/license"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
payload = {
"licenseKey": license_key
}
try:
logging.info("Adding or updating license...")
response = requests.put(url, json=payload, headers=headers, verify=self.client.verify_certificate)
# Handle empty response with 200 status code
if response.status_code == 200 and not response.content:
logging.info("License successfully added or updated with no content returned.")
return {}
# Raise an error for other non-successful HTTP status codes
response.raise_for_status()
# Parse the response JSON
response_data = response.json()
logging.info("Successfully added or updated license.")
return response_data
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 details: {json.dumps(error_details, indent=2)}")
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 while adding or updating license: {e}")
raise
def delete_license(self):
"""
Delete the current license from the Zerto server.
Returns:
dict: The response from the Zerto server.
"""
logging.info(f'License.delete_license(zvm_address={self.client.zvm_address})')
url = f"https://{self.client.zvm_address}/v1/license"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
try:
logging.info("Deleting license...")
response = requests.delete(url, headers=headers, verify=self.client.verify_certificate)
# Raise an error for non-successful HTTP status codes
response.raise_for_status()
# Parse the response JSON if available
if response.content:
response_data = response.json()
logging.info("License successfully deleted.")
return response_data
else:
logging.info("License successfully deleted with no content returned.")
return {}
except requests.exceptions.RequestException as e:
logging.error(f"HTTPError: {e.response.status_code} - {e.response.reason}")
try:
error_details = e.response.json()
logging.error(f"Error details: {json.dumps(error_details, indent=2)}")
except ValueError:
logging.error(f"Response content: {e.response.text}")
raise
except Exception as e:
logging.error(f"Unexpected error while deleting license: {e}")
raise
+151
View File
@@ -0,0 +1,151 @@
# 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
class LocalSite:
def __init__(self, zvm_address, token):
self.zvm_address = zvm_address
self.token = token
self.headers = {
"Authorization": f"Bearer {self.token}",
"Accept": "application/json",
"Content-Type": "application/json"
}
def get_local_site(self):
logging.info("LocalSite.get_local_site: Fetching local site information...")
url = f"https://{self.zvm_address}/v1/localsite"
try:
response = requests.get(url, headers=self.headers, verify=False)
response.raise_for_status()
logging.info("LocalSite.get_local_site: Successfully retrieved local site information.")
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 get_pairing_statuses(self):
logging.info("LocalSite.get_pairing_statuses: Fetching pairing statuses...")
url = f"https://{self.zvm_address}/v1/localsite/pairingstatuses"
try:
response = requests.get(url, headers=self.headers, verify=False)
response.raise_for_status()
logging.info("LocalSite.get_pairing_statuses: Successfully retrieved pairing statuses.")
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 send_usage(self):
logging.info("LocalSite.send_usage: Sending local site billing usage...")
url = f"https://{self.zvm_address}/v1/localsite/billing/sendUsage"
try:
response = requests.post(url, headers=self.headers, verify=False)
response.raise_for_status()
if response.content.strip():
logging.info("LocalSite.send_usage: Successfully sent billing usage data.")
return response.json()
else:
logging.info("LocalSite.send_usage: Successfully sent billing usage data. No content returned.")
return 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
except Exception as e:
logging.error(f"Unexpected error: {e}")
raise
def get_login_banner(self):
logging.info("LocalSite.get_login_banner: Fetching login banner settings...")
url = f"https://{self.zvm_address}/v1/localsite/settings/loginBanner"
try:
response = requests.get(url, headers=self.headers, verify=False)
response.raise_for_status()
logging.info("LocalSite.get_login_banner: Successfully retrieved login banner settings.")
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 set_login_banner(self, is_enabled, banner_text):
logging.info("LocalSite.set_login_banner: Setting login banner settings...")
url = f"https://{self.zvm_address}/v1/localsite/settings/loginBanner"
payload = {
"isLoginBannerEnabled": is_enabled,
"loginBanner": banner_text
}
try:
response = requests.put(url, headers=self.headers, json=payload, verify=False)
response.raise_for_status()
logging.info("LocalSite.set_login_banner: Successfully set login banner settings.")
return response
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
+34
View File
@@ -0,0 +1,34 @@
# 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 argparse
import logging
from zvml import ZVMLClient
def main():
parser = argparse.ArgumentParser(description="zvml Client")
parser.add_argument("--zvm_address", required=True, help="ZVM address")
parser.add_argument("--username", required=True, help="Username")
parser.add_argument("--password", required=True, help="Password")
args = parser.parse_args()
logging.basicConfig(level=logging.INFO)
try:
client = ZVMLClient(zvm_address=args.zvm_address, username=args.username, password=args.password)
# Example usage
vpgs = client.vpgs.list_vpgs()
logging.info(f"VPGs: {vpgs}")
except Exception as e:
logging.error(f"Error: {e}")
if __name__ == "__main__":
main()
+275
View File
@@ -0,0 +1,275 @@
# 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 time
from .tasks import Tasks
class PeerSites:
def __init__(self, client):
self.client = client
self.tasks = Tasks(client)
def get_peer_sites(self):
"""
Get details of all peer sites paired with this site. (Auth)
Returns:
list: List of peer sites
"""
url = f"https://{self.client.zvm_address}/v1/peersites"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
logging.info("PeerSites.get_peer_sites: Fetching all peer sites...")
try:
response = requests.get(url, headers=headers, 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
except Exception as e:
logging.error(f"Unexpected error: {e}")
raise
def pair_site(self, hostname, token, port=9071, sync=True):
"""
Pairs this site with another site. (Auth)
Args:
hostname (str): The IP or DNS name for the peer site
token (str): The pairing token generated from the peer site
port (int, optional): The port used to access the peer site. Defaults to 9071.
sync (bool, optional): Wait for the pairing task to complete. Defaults to True.
Returns:
dict: Pairing result if sync=False, or final task status if sync=True
"""
url = f"https://{self.client.zvm_address}/v1/peersites"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
pairing_data = {
"hostName": hostname,
"port": port,
"token": token
}
logging.info(f"PeerSites.pair_site: Pairing with site {hostname} at port {port}...")
try:
response = requests.post(url, headers=headers, json=pairing_data, verify=self.client.verify_certificate)
response.raise_for_status()
if not sync:
return response.json() if response.content else None
# Get the task identifier from the response
task_id = response.json()
logging.info(f"PeerSites.pair_site pairing submitted, task_id={task_id}")
if sync:
# Wait for task completion
self.tasks.wait_for_task_completion(task_id, timeout=30, interval=5)
return task_id
return task_id
except requests.exceptions.RequestException as e:
logging.error(f"Error pairing site: {str(e)}")
if hasattr(e, 'response') and e.response is not None:
logging.error(f"Response content: {e.response.text}")
raise
def delete_peer_site(self, site_identifier, sync=True):
"""
Unpairs this site with another site. (Auth)
Args:
site_identifier (str): The identifier of the peer site to delete
sync (bool, optional): Wait for the pairing task to complete. Defaults to True.
"""
url = f"https://{self.client.zvm_address}/v1/peersites/{site_identifier}"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
logging.info(f"PeerSites.delete_peer_site: Deleting peer site {site_identifier}...")
try:
response = requests.delete(url, headers=headers, verify=self.client.verify_certificate)
response.raise_for_status()
if not sync:
return response.json() if response.content else None
# Get the task identifier from the response
task_id = response.json()
logging.info(f"PeerSites.delete_peer_site unpairing submitted, task_id={task_id}")
if sync:
# Wait for task completion
self.tasks.wait_for_task_completion(task_id, timeout=30, interval=5)
return task_id
return task_id
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 get_pairing_statuses(self):
"""
Get the list of possible statuses for peer sites pairing. (Auth)
Returns:
list: List of possible pairing statuses
"""
url = f"https://{self.client.zvm_address}/v1/peersites/pairingstatuses"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
logging.info("PeerSites.get_pairing_statuses: Fetching pairing statuses...")
try:
response = requests.get(url, headers=headers, 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
except Exception as e:
logging.error(f"Unexpected error: {e}")
raise
def generate_token(self):
"""
Generate a token to pair with this site. (Auth)
Returns:
str: Generated pairing token
"""
url = f"https://{self.client.zvm_address}/v1/peersites/generatetoken"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
logging.info("PeerSites.generate_token: Generating pairing token...")
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
except Exception as e:
logging.error(f"Unexpected error: {e}")
raise
def get_peer_site(self, site_identifier):
logging.info(f"PeerSites.get_peer_site: Fetching peer site information for site identifier: {site_identifier}...")
url = f"https://{self.client.zvm_address}/v1/peersites/{site_identifier}"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
try:
response = requests.get(url, headers=headers, verify=self.client.verify_certificate)
response.raise_for_status()
logging.info(f"PeerSites.get_peer_site: Successfully retrieved peer site information for site identifier: {site_identifier}.")
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 get_peer_site_types(self):
logging.info("PeerSites.get_peer_site_types: Fetching peer site information for site types...")
url = f"https://{self.client.zvm_address}/v1/peersites/types"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
try:
response = requests.get(url, headers=headers, verify=self.client.verify_certificate)
response.raise_for_status()
logging.info("PeerSites.get_peer_site_types: Successfully retrieved peer site types information.")
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
+255
View File
@@ -0,0 +1,255 @@
# 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 RecoveryReports:
def __init__(self, client):
self.client = client
def get_recovery_reports(self, recovery_operation_identifier=None, page_number=1, page_size=1000,
vpg_name=None, recovery_type=None, state=None, start_time=None, end_time=None):
"""
Generate a recovery report and view information about recovery operations.
Args:
recovery_operation_identifier (str): The identifier of a specific recovery operation. If provided, no other parameters are used.
start_time (str): The filtering interval start date-time.
end_time (str): The filtering interval end date-time.
page_number (int): The page number to retrieve. Default is 1.
page_size (int): The number of reports to display in a single page. Max 1000. Default is 1000.
vpg_name (str): The name of the VPG(s) to filter by. Separate multiple VPGs with commas.
recovery_type (str): The type of recovery operation. Possible values: Failover, FailoverTest, Move.
state (str): The recovery operation state. Possible values: Success, Fail.
Returns:
dict: The response from the ZVM API containing recovery report details.
"""
logging.info(f'RecoveryReports.get_recovery_reports recovery_operation_identifier: {recovery_operation_identifier}, \
start_time: {start_time}, end_time: {end_time}, page_number: {page_number}, \
page_size: {page_size}, vpg_name: {vpg_name}, recovery_type: {recovery_type}, state: {state}')
# Determine the URL based on whether recoveryOperationIdentifier is provided
if recovery_operation_identifier:
base_url = f"https://{self.client.zvm_address}/v1/reports/recovery/{recovery_operation_identifier}"
params = None # No query parameters for this endpoint
else:
base_url = f"https://{self.client.zvm_address}/v1/reports/recovery"
# Parameters for the request
params = {
"startTime": start_time,
"endTime": end_time,
"pageNumber": page_number,
"pageSize": page_size,
}
# Optional parameters
if vpg_name:
params["vpgName"] = vpg_name
if recovery_type:
params["recoveryType"] = recovery_type
if state:
params["state"] = state
# Headers for the request
headers = {
"Authorization": f"Bearer {self.client.token}",
"Accept": "application/json",
}
try:
response = requests.get(base_url, headers=headers, params=params, verify=self.client.verify_certificate)
if response.status_code == 200:
# logging.info(f"Successfully retrieved recovery reports = {json.dumps(response.json(), indent=4)}")
return response.json()
else:
logging.error(f"Failed to fetch recovery reports. Status Code: {response.status_code}, Response: {response.text}")
response.raise_for_status()
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 list_resource_reports(self, start_time=None, end_time=None, page_number=None, page_size=None,
zorg_name=None, vpg_name=None, vm_name=None, protected_site_name=None,
protected_cluster_name=None, protected_host_name=None, protected_org_vdc=None,
protected_vcd_org=None, recovery_site_name=None, recovery_cluster_name=None,
recovery_host_name=None, recovery_org_vdc=None, recovery_vcd_org=None):
"""
Fetch resource reports with optional filters.
Args:
start_time (str): The filtering interval start date-time.
end_time (str): The filtering interval end date-time.
page_number (int): The page number to retrieve.
page_size (int): The number of reports per page (max 1000).
zorg_name (str): The name of the ZORG in the Zerto Cloud Manager.
vpg_name (str): The name of the VPG.
vm_name (str): The name of the virtual machine.
protected_site_name (str): The name of the protected site.
protected_cluster_name (str): The name of the protected cluster.
protected_host_name (str): The name of the protected host.
protected_org_vdc (str): The name of the protected VDC organization.
protected_vcd_org (str): The name of the protected VCD organization.
recovery_site_name (str): The name of the recovery site.
recovery_cluster_name (str): The name of the recovery cluster.
recovery_host_name (str): The name of the recovery host.
recovery_org_vdc (str): The name of the recovery VDC organization.
recovery_vcd_org (str): The name of the recovery VCD organization.
Returns:
list: A list of resource reports based on the provided filters.
"""
logging.info(f"list_resource_reports(start_time={start_time}, end_time={end_time}, page_number={page_number}, "
f"page_size={page_size}, zorg_name={zorg_name}, vpg_name={vpg_name}, vm_name={vm_name}, "
f"protected_site_name={protected_site_name}, protected_cluster_name={protected_cluster_name}, "
f"protected_host_name={protected_host_name}, protected_org_vdc={protected_org_vdc}, "
f"protected_vcd_org={protected_vcd_org}, recovery_site_name={recovery_site_name}, "
f"recovery_cluster_name={recovery_cluster_name}, recovery_host_name={recovery_host_name}, "
f"recovery_org_vdc={recovery_org_vdc}, recovery_vcd_org={recovery_vcd_org})")
uri = f"https://{self.client.zvm_address}/v1/reports/resources"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
# Building query parameters
params = {}
if start_time:
params['startTime'] = start_time
if end_time:
params['endTime'] = end_time
if page_number is not None:
params['pageNumber'] = page_number
if page_size is not None:
params['pageSize'] = page_size
if zorg_name:
params['zorgName'] = zorg_name
if vpg_name:
params['vpgName'] = vpg_name
if vm_name:
params['vmName'] = vm_name
if protected_site_name:
params['protectedSiteName'] = protected_site_name
if protected_cluster_name:
params['protectedClusterName'] = protected_cluster_name
if protected_host_name:
params['protectedHostName'] = protected_host_name
if protected_org_vdc:
params['protectedOrgVdc'] = protected_org_vdc
if protected_vcd_org:
params['protectedVcdOrg'] = protected_vcd_org
if recovery_site_name:
params['recoverySiteName'] = recovery_site_name
if recovery_cluster_name:
params['recoveryClusterName'] = recovery_cluster_name
if recovery_host_name:
params['recoveryHostName'] = recovery_host_name
if recovery_org_vdc:
params['recoveryOrgVdc'] = recovery_org_vdc
if recovery_vcd_org:
params['recoveryVcdOrg'] = recovery_vcd_org
try:
response = requests.get(uri, headers=headers, params=params, verify=self.client.verify_certificate)
response.raise_for_status()
reports = response.json()
if not reports:
logging.warning("No resource reports found.")
return []
return reports
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 get_latest_failover_test_report(self, vpg_name):
"""
Get the most recent failover test report for a specific VPG.
Args:
vpg_name (str): The name of the VPG to get the report for.
Returns:
dict: The most recent failover test report for the VPG, or None if no reports found.
"""
logging.info(f"RecoveryReports.get_latest_failover_test_report VPG: {vpg_name}")
try:
# Get all failover test reports for this VPG
reports = self.get_recovery_reports(
vpg_name=vpg_name,
recovery_type="FailoverTest",
page_size=1000 # Adjust if you need more reports
)
if not reports:
logging.warning(f"No failover test reports found for VPG: {vpg_name}")
return None
# Sort reports by StartTime in descending order and get the first one
sorted_reports = sorted(
reports,
key=lambda x: x["General"].get("EndTime", ""),
reverse=True
)
if sorted_reports:
return sorted_reports[0]
return 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
except Exception as e:
logging.error(f"Unexpected error: {e}")
raise
+93
View File
@@ -0,0 +1,93 @@
# 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
class RecoveryScripts:
def __init__(self, client):
self.client = client
def get_recovery_scripts(self):
url = f"https://{self.client.zvm_address}/v1/recoveryscripts"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
try:
response = requests.get(url, headers=headers, 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
except Exception as e:
logging.error(f"Unexpected error: {e}")
raise
def get_recovery_script(self, script_identifier):
url = f"https://{self.client.zvm_address}/v1/recoveryscripts/{script_identifier}"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
try:
response = requests.get(url, headers=headers, 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
except Exception as e:
logging.error(f"Unexpected error: {e}")
raise
def get_recovery_script_types(self):
url = f"https://{self.client.zvm_address}/v1/recoveryscripts/types"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
try:
response = requests.get(url, headers=headers, 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
except Exception as e:
logging.error(f"Unexpected error: {e}")
raise
+95
View File
@@ -0,0 +1,95 @@
# 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
class Repositories:
def __init__(self, client):
self.client = client
def get_repositories(self):
url = f"https://{self.client.zvm_address}/v1/repositories"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
try:
response = requests.get(url, headers=headers, 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
except Exception as e:
logging.error(f"Unexpected error: {e}")
raise
def get_repository(self, repository_identifier):
url = f"https://{self.client.zvm_address}/v1/repositories/{repository_identifier}"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
try:
response = requests.get(url, headers=headers, 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
except Exception as e:
logging.error(f"Unexpected error: {e}")
raise
def get_repository_types(self):
url = f"https://{self.client.zvm_address}/v1/repositories/types"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
try:
response = requests.get(url, headers=headers, 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
except Exception as e:
logging.error(f"Unexpected error: {e}")
raise
+72
View File
@@ -0,0 +1,72 @@
# 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
from enum import Enum
class DateTimeFormat(Enum):
"""Enum for date time format options"""
DEFAULT = "" # Returns full server time info
LOCAL = "serverDateTimeLocal" # Returns local time
UTC = "serverDateTimeUtc" # Returns UTC time
ARGUMENT = "dateTimeArgument" # Returns date time argument format
class ServerDateTime:
def __init__(self, client):
self.client = client
def get_server_date_time(self, format=DateTimeFormat.DEFAULT):
"""
Get the server date and time in the specified format.
Args:
format (DateTimeFormat): The format to return the date-time in.
- DEFAULT: Returns full server time info (timezone, UTC time, local time, offset)
- LOCAL: Returns local time
- UTC: Returns UTC time
- ARGUMENT: Returns date time argument format
Returns:
dict/str: Server date-time information. Format depends on the format parameter:
- DEFAULT: {
'TimeZone': str,
'ServerTimeUtc': str,
'LocalTime': str,
'TimeOffset': str
}
- LOCAL: Local time string
- UTC: UTC time string
- ARGUMENT: Date time argument format string
"""
logging.info(f"ServerDateTime.get_server_date_time: Fetching server date and time in {format.name} format...")
# Build the URL based on the format
base_url = f"https://{self.client.zvm_address}/v1/serverDateTime"
url = base_url if format == DateTimeFormat.DEFAULT else f"{base_url}/{format.value}"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
try:
response = requests.get(url, headers=headers, verify=self.client.verify_certificate)
response.raise_for_status()
server_time = response.json()
logging.info(f"Successfully retrieved server date and time in {format.name} format")
return server_time
except requests.exceptions.RequestException as e:
logging.error(f"Failed to get server date and time: {e}")
if hasattr(e.response, 'text'):
logging.error(f"Error response: {e.response.text}")
raise
+61
View File
@@ -0,0 +1,61 @@
# 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
class ServiceProfiles:
def __init__(self, client):
self.client = client
def get_service_profiles(self, site_identifier=None):
"""
Get the list of all service profiles for the site.
Args:
site_identifier (str, optional): The identifier of the site for which service profiles
should be returned.
Returns:
list: List of service profiles with their details including:
- serviceProfileName: Name of the service profile
- rpo: Recovery Point Objective
- history: Journal history length
- maxJournalSizeInPercent: Maximum journal size as percentage
- testInterval: Test interval period
- description: Service profile description
"""
logging.info(f"ServiceProfiles.get_service_profiles: Fetching service profiles...")
url = f"https://{self.client.zvm_address}/v1/serviceprofiles"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
params = {}
if site_identifier:
params['siteIdentifier'] = site_identifier
logging.info(f"Filtering service profiles for site: {site_identifier}")
try:
response = requests.get(url, headers=headers, params=params, verify=self.client.verify_certificate)
response.raise_for_status()
profiles = response.json()
logging.info(f"Successfully retrieved {len(profiles)} service profiles")
return profiles
except requests.exceptions.RequestException as e:
logging.error(f"Failed to get service profiles: {e}")
if hasattr(e.response, 'text'):
logging.error(f"Error response: {e.response.text}")
raise
+59
View File
@@ -0,0 +1,59 @@
# 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
class Sessions:
def __init__(self, client):
self.client = client
def get_sessions(self):
url = f"https://{self.client.zvm_address}/v1/sessions"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
try:
response = requests.get(url, headers=headers, verify=self.client.verify_certificate)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
logging.error(f"Failed to get sessions: {e}")
raise
def get_session(self, session_identifier):
url = f"https://{self.client.zvm_address}/v1/sessions/{session_identifier}"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
try:
response = requests.get(url, headers=headers, verify=self.client.verify_certificate)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
logging.error(f"Failed to get session: {e}")
raise
def get_session_types(self):
url = f"https://{self.client.zvm_address}/v1/sessions/types"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
try:
response = requests.get(url, headers=headers, verify=self.client.verify_certificate)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
logging.error(f"Failed to get session types: {e}")
raise
+57
View File
@@ -0,0 +1,57 @@
# 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 time
from .common import ZertoTaskStates
class Tasks:
def __init__(self, client):
self.client = client
def wait_for_task_completion(self, task_identifier, timeout=600, interval=5, expected_task_state: ZertoTaskStates = ZertoTaskStates.Completed):
logging.debug(f'wait_for_task_completion(zvm_address={self.client.zvm_address}, task_identifier={task_identifier}, timeout={timeout}, interval={interval})')
start_time = time.time()
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
while True:
# Check if we've exceeded the timeout
if time.time() - start_time > timeout:
logging.error(f'Task ID={task_identifier} timed out after {timeout} seconds')
raise TimeoutError(f"Task did not complete within {timeout} seconds")
url = f"https://{self.client.zvm_address}/v1/tasks/{task_identifier}"
try:
response = requests.get(url, headers=headers, verify=self.client.verify_certificate)
response.raise_for_status()
task_info = response.json()
state = task_info.get("Status", {}).get("State", -1)
progress = task_info.get("Status", {}).get("Progress", 0)
logging.debug(f'Task response: status={ZertoTaskStates.get_name_by_value(state)}, progress={progress}')
if state == expected_task_state.value and progress == 100:
logging.info("Task completed successfully.")
time.sleep(interval)
return task_info
elif state == ZertoTaskStates.InProgress.value:
time.sleep(interval)
continue
else:
logging.error(f'Task ID={task_identifier} failed. task state={ZertoTaskStates.get_name_by_value(state)}')
raise Exception(f"Task failed: {task_info.get('CompleteReason', 'No reason provided')}")
except requests.exceptions.RequestException as e:
logging.error(f"Request failed: {e}")
raise
+179
View File
@@ -0,0 +1,179 @@
# 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
from typing import Dict, List, Optional, Any
from zvml.common import ZertoTweakType
class Tweaks:
def __init__(self, client):
self.client = client
def list_tweaks(self, tweak_name: Optional[str] = None) -> List[Dict]:
"""List ZVM tweaks.
Args:
tweak_name: Optional name of specific tweak to retrieve
Returns:
List[Dict]: List of ZVM tweaks and their current settings, or a single tweak if name provided
Raises:
requests.exceptions.RequestException: If the API request fails
"""
logging.info(f"Tweaks.list_tweaks(zvm_address={self.client.zvm_address}, tweak_name={tweak_name})")
# Build URL based on whether a specific tweak is requested
base_url = f"https://{self.client.zvm_address}/management/api/tweaks/v1.0/zvmTweaks"
url = f"{base_url}/{tweak_name}" if tweak_name else base_url
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
try:
response = requests.get(url, headers=headers, verify=self.client.verify_certificate)
response.raise_for_status()
result = response.json()
# If a specific tweak was requested, wrap the result in a list for consistent return type
if tweak_name:
result = [result]
logging.info(f"Successfully retrieved {len(result)} ZVM tweak(s)")
logging.debug(f"Tweaks.list_tweaks result: {json.dumps(result, indent=4)}")
return result
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 set_tweak(self, tweak_name: str, value: Any, tweak_type: ZertoTweakType = ZertoTweakType.ZVM, comment: str = "Changed from API") -> Dict:
"""Set a ZVM tweak value.
Args:
tweak_name: Name of the tweak to update
value: New value for the tweak
tweak_type: Type of tweak (ZVM, VRA, or Frontend)
comment: Optional comment for the change
Returns:
Dict: Updated tweak information
Raises:
requests.exceptions.RequestException: If the API request fails
"""
logging.info(f"Tweaks.set_tweak(zvm_address={self.client.zvm_address}, tweak_name={tweak_name}, value={value}, type={tweak_type.value})")
url = f"https://{self.client.zvm_address}/management/api/tweaks/v1/zvmTweaks"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
payload = {
"name": tweak_name,
"type": tweak_type.value,
"value": str(value),
"comment": comment
}
logging.info(f"Tweaks.set_tweak payload: {payload}")
logging.info(f"Tweaks.set_tweak url: {url}")
try:
response = requests.post(url, headers=headers, json=payload, verify=self.client.verify_certificate)
# Log the raw response for debugging
logging.debug(f"Raw response status: {response.status_code}")
logging.debug(f"Raw response headers: {dict(response.headers)}")
logging.debug(f"Raw response content: {response.text}")
response.raise_for_status()
try:
result = response.json()
logging.info(f"Successfully updated tweak {tweak_name}")
logging.debug(f"Tweaks.set_tweak result: {json.dumps(result, indent=4)}")
return result
except ValueError:
# If response is not JSON but request was successful
logging.info(f"Successfully updated tweak {tweak_name} (no JSON response)")
return {"status": "success", "name": tweak_name}
except requests.exceptions.RequestException as e:
logging.error(f"Error setting tweak {tweak_name}")
if hasattr(e, 'response') and e.response is not None:
logging.error(f"Status code: {e.response.status_code}")
logging.error(f"Response text: {e.response.text}")
try:
error_json = e.response.json()
logging.error(f"Error details: {json.dumps(error_json, indent=2)}")
except ValueError:
logging.error("Could not parse error response as JSON")
else:
logging.error(f"Request failed: {str(e)}")
raise
def delete_tweak(self, tweak_name: str) -> None:
"""Delete a ZVM tweak.
Args:
tweak_name: Name of the tweak to delete
Raises:
requests.exceptions.RequestException: If the API request fails
"""
logging.info(f"Tweaks.delete_tweak(zvm_address={self.client.zvm_address}, tweak_name={tweak_name})")
url = f"https://{self.client.zvm_address}/management/api/tweaks/v1/zvmTweaks/{tweak_name}"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
logging.info(f"Tweaks.delete_tweak url: {url}")
try:
response = requests.delete(url, headers=headers, verify=self.client.verify_certificate)
# Log the raw response for debugging
logging.debug(f"Raw response status: {response.status_code}")
logging.debug(f"Raw response headers: {dict(response.headers)}")
logging.debug(f"Raw response content: {response.text}")
response.raise_for_status()
logging.info(f"Successfully deleted tweak {tweak_name}")
except requests.exceptions.RequestException as e:
logging.error(f"Error deleting tweak {tweak_name}")
if hasattr(e, 'response') and e.response is not None:
logging.error(f"Status code: {e.response.status_code}")
logging.error(f"Response text: {e.response.text}")
try:
error_json = e.response.json()
logging.error(f"Error details: {json.dumps(error_json, indent=2)}")
except ValueError:
logging.error("Could not parse error response as JSON")
else:
logging.error(f"Request failed: {str(e)}")
raise
+989
View File
@@ -0,0 +1,989 @@
# 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
class VirtualizationSites:
def __init__(self, client):
self.client = client
def get_virtualization_sites(self, site_identifier=None):
"""
Get virtualization sites information. (Auth)
Args:
site_identifier (str, optional): The identifier of the site to get details for.
If not provided, returns all sites.
Endpoints:
- /v1/virtualizationsites (when site_identifier is None)
- /v1/virtualizationsites/{siteIdentifier} (when site_identifier is provided)
Returns:
dict or list: Site details if site_identifier is provided, otherwise array of all sites
"""
url = f"https://{self.client.zvm_address}/v1/virtualizationsites"
if site_identifier:
url = f"{url}/{site_identifier}"
logging.info(f"VirtualizationSites.get_virtualization_sites: Fetching site {site_identifier}...")
else:
logging.info("VirtualizationSites.get_virtualization_sites: Fetching all virtualization sites...")
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
try:
response = requests.get(url, headers=headers, 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 get_virtualization_site_vms(self, site_identifier):
"""
Get a list of unprotected VMs from the specified site. (Auth)
Args:
site_identifier (str): The identifier of the site to get VMs from.
Endpoint:
/v1/virtualizationsites/{siteIdentifier}/vms
Returns:
list: Array of unprotected VMs in the specified site
Raises:
requests.exceptions.RequestException: If the API request fails
"""
url = f"https://{self.client.zvm_address}/v1/virtualizationsites/{site_identifier}/vms"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
logging.info(f"VirtualizationSites.get_virtualization_site_vms: Fetching VMs for site {site_identifier}...")
try:
response = requests.get(url, headers=headers, 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 get_virtualization_site_vcd_vapps(self, site_identifier):
"""
Get a list of unprotected VCD vApps from the specified site. (Auth)
Args:
site_identifier (str): The identifier of the site to get VCD vApps from.
Endpoint:
/v1/virtualizationsites/{siteIdentifier}/vcdvapps
Returns:
list: Array of unprotected VCD vApps in the specified site
Raises:
requests.exceptions.RequestException: If the API request fails
"""
url = f"https://{self.client.zvm_address}/v1/virtualizationsites/{site_identifier}/vcdvapps"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
logging.info(f"VirtualizationSites.get_virtualization_site_vcd_vapps: Fetching VCD vApps for site {site_identifier}...")
try:
response = requests.get(url, headers=headers, 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 get_virtualization_site_datastores(self, site_identifier):
"""
Get a list of datastores from the specified site. (Auth)
Args:
site_identifier (str): The identifier of the site to get datastores from.
Endpoint:
/v1/virtualizationsites/{siteIdentifier}/datastores
Returns:
list: Array of datastores in the specified site
Raises:
requests.exceptions.RequestException: If the API request fails
"""
url = f"https://{self.client.zvm_address}/v1/virtualizationsites/{site_identifier}/datastores"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
logging.info(f"VirtualizationSites.get_virtualization_site_datastores: Fetching datastores for site {site_identifier}...")
try:
response = requests.get(url, headers=headers, 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 get_virtualization_site_folders(self, site_identifier):
"""
Get a list of folders from the specified site. (Auth)
Args:
site_identifier (str): The identifier of the site to get folders from.
Endpoint:
/v1/virtualizationsites/{siteIdentifier}/folders
Returns:
list: Array of folders in the specified site
Raises:
requests.exceptions.RequestException: If the API request fails
"""
url = f"https://{self.client.zvm_address}/v1/virtualizationsites/{site_identifier}/folders"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
logging.info(f"VirtualizationSites.get_virtualization_site_folders: Fetching folders for site {site_identifier}...")
try:
response = requests.get(url, headers=headers, 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 get_virtualization_site_datastore_clusters(self, site_identifier):
"""
Get a list of datastore clusters from the specified site. (Auth)
Args:
site_identifier (str): The identifier of the site to get datastore clusters from.
Endpoint:
/v1/virtualizationsites/{siteIdentifier}/datastoreclusters
Returns:
list: Array of datastore clusters in the specified site
Raises:
requests.exceptions.RequestException: If the API request fails
"""
url = f"https://{self.client.zvm_address}/v1/virtualizationsites/{site_identifier}/datastoreclusters"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
logging.info(f"VirtualizationSites.get_virtualization_site_datastore_clusters: Fetching datastore clusters for site {site_identifier}...")
try:
response = requests.get(url, headers=headers, 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 get_virtualization_site_resource_pools(self, site_identifier):
"""
Get a list of resource pools from the specified site. (Auth)
Args:
site_identifier (str): The identifier of the site to get resource pools from.
Endpoint:
/v1/virtualizationsites/{siteIdentifier}/resourcepools
Returns:
list: Array of resource pools in the specified site
Raises:
requests.exceptions.RequestException: If the API request fails
"""
url = f"https://{self.client.zvm_address}/v1/virtualizationsites/{site_identifier}/resourcepools"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
logging.info(f"VirtualizationSites.get_virtualization_site_resource_pools: Fetching resource pools for site {site_identifier}...")
try:
response = requests.get(url, headers=headers, 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 get_virtualization_site_org_vdcs(self, site_identifier):
"""
Get a list of organization VDCs (Virtual Data Centers) from the specified site. (Auth)
Args:
site_identifier (str): The identifier of the site to get org VDCs from.
Endpoint:
/v1/virtualizationsites/{siteIdentifier}/orgvdcs
Returns:
list: Array of organization VDCs in the specified site
Raises:
requests.exceptions.RequestException: If the API request fails
"""
url = f"https://{self.client.zvm_address}/v1/virtualizationsites/{site_identifier}/orgvdcs"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
logging.info(f"VirtualizationSites.get_virtualization_site_org_vdcs: Fetching org VDCs for site {site_identifier}...")
try:
response = requests.get(url, headers=headers, 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 get_virtualization_site_networks(self, site_identifier):
"""
Get a list of networks from the specified site. (Auth)
Args:
site_identifier (str): The identifier of the site to get networks from.
Endpoint:
/v1/virtualizationsites/{siteIdentifier}/networks
Returns:
list: Array of networks in the specified site
Raises:
requests.exceptions.RequestException: If the API request fails
"""
url = f"https://{self.client.zvm_address}/v1/virtualizationsites/{site_identifier}/networks"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
logging.info(f"VirtualizationSites.get_virtualization_site_networks: Fetching networks for site {site_identifier}...")
try:
response = requests.get(url, headers=headers, 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 get_virtualization_site_hosts(self, site_identifier, host_identifier=None):
"""
Get hosts information from the specified site. (Auth)
Args:
site_identifier (str): The identifier of the site to get hosts from.
host_identifier (str, optional): The identifier of a specific host to get details for.
If not provided, returns all hosts.
Endpoints:
- /v1/virtualizationsites/{siteIdentifier}/hosts (when host_identifier is None)
- /v1/virtualizationsites/{siteIdentifier}/hosts/{hostIdentifier} (when host_identifier is provided)
Returns:
list or dict: Array of hosts if host_identifier is None, otherwise details of specific host
Raises:
requests.exceptions.RequestException: If the API request fails
"""
url = f"https://{self.client.zvm_address}/v1/virtualizationsites/{site_identifier}/hosts"
if host_identifier:
url = f"{url}/{host_identifier}"
logging.info(f"VirtualizationSites.get_virtualization_site_hosts: Fetching host {host_identifier} from site {site_identifier}...")
else:
logging.info(f"VirtualizationSites.get_virtualization_site_hosts: Fetching all hosts for site {site_identifier}...")
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
try:
response = requests.get(url, headers=headers, 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 get_virtualization_site_repositories(self, site_identifier):
"""
Get a list of repositories from the specified site. (Auth)
Args:
site_identifier (str): The identifier of the site to get repositories from.
Endpoint:
/v1/virtualizationsites/{siteIdentifier}/repositories
Returns:
list: Array of repositories in the specified site
Raises:
requests.exceptions.RequestException: If the API request fails
"""
url = f"https://{self.client.zvm_address}/v1/virtualizationsites/{site_identifier}/repositories"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
logging.info(f"VirtualizationSites.get_virtualization_site_repositories: Fetching repositories for site {site_identifier}...")
try:
response = requests.get(url, headers=headers, 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 get_virtualization_site_host_clusters(self, site_identifier):
"""
Get a list of host clusters from the specified site. (Auth)
Args:
site_identifier (str): The identifier of the site to get host clusters from.
Endpoint:
/v1/virtualizationsites/{siteIdentifier}/hostclusters
Returns:
list: Array of host clusters in the specified site
Raises:
requests.exceptions.RequestException: If the API request fails
"""
url = f"https://{self.client.zvm_address}/v1/virtualizationsites/{site_identifier}/hostclusters"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
logging.info(f"VirtualizationSites.get_virtualization_site_host_clusters: Fetching host clusters for site {site_identifier}...")
try:
response = requests.get(url, headers=headers, 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 get_virtualization_site_org_vdc_networks(self, site_identifier, org_vdc_identifier):
"""
Get a list of networks from the specified organization VDC. (Auth)
Args:
site_identifier (str): The identifier of the site.
org_vdc_identifier (str): The identifier of the organization VDC to get networks from.
Endpoint:
/v1/virtualizationsites/{siteIdentifier}/orgvdcs/{orgVdcIdentifier}/networks
Returns:
list: Array of networks in the specified organization VDC
Raises:
requests.exceptions.RequestException: If the API request fails
"""
url = f"https://{self.client.zvm_address}/v1/virtualizationsites/{site_identifier}/orgvdcs/{org_vdc_identifier}/networks"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
logging.info(f"VirtualizationSites.get_virtualization_site_org_vdc_networks: Fetching networks for org VDC {org_vdc_identifier} in site {site_identifier}...")
try:
response = requests.get(url, headers=headers, 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 get_virtualization_site_org_vdc_storage_policies(self, site_identifier, org_vdc_identifier):
"""
Get a list of storage policies from the specified organization VDC. (Auth)
Args:
site_identifier (str): The identifier of the site.
org_vdc_identifier (str): The identifier of the organization VDC to get storage policies from.
Endpoint:
/v1/virtualizationsites/{siteIdentifier}/orgvdcs/{orgVdcIdentifier}/storagepolicies
Returns:
list: Array of storage policies in the specified organization VDC
Raises:
requests.exceptions.RequestException: If the API request fails
"""
url = f"https://{self.client.zvm_address}/v1/virtualizationsites/{site_identifier}/orgvdcs/{org_vdc_identifier}/storagepolicies"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
logging.info(f"VirtualizationSites.get_virtualization_site_org_vdc_storage_policies: Fetching storage policies for org VDC {org_vdc_identifier} in site {site_identifier}...")
try:
response = requests.get(url, headers=headers, 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 get_virtualization_site_devices(self, site_identifier, host_identifier=None, device_name=None):
"""
Get a list of devices from the specified site. (Auth)
Args:
site_identifier (str): The identifier of the site to get devices from.
host_identifier (str, optional): Filter devices by host identifier.
device_name (str, optional): Filter devices by device name.
Endpoint:
/v1/virtualizationsites/{siteIdentifier}/devices
Query Parameters:
- hostIdentifier (optional)
- deviceName (optional)
Returns:
list: Array of devices in the specified site
Raises:
requests.exceptions.RequestException: If the API request fails
"""
url = f"https://{self.client.zvm_address}/v1/virtualizationsites/{site_identifier}/devices"
# Add query parameters if provided
params = {}
if host_identifier:
params['hostIdentifier'] = host_identifier
if device_name:
params['deviceName'] = device_name
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
logging.info(f"VirtualizationSites.get_virtualization_site_devices: Fetching devices for site {site_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 get_virtualization_site_public_cloud_networks(self, site_identifier):
"""
Get a list of public cloud virtual networks from the specified site. (Auth)
Args:
site_identifier (str): The identifier of the site to get public cloud virtual networks from.
Endpoint:
/v1/virtualizationsites/{siteIdentifier}/publiccloud/virtualNetworks
Returns:
list: Array of public cloud virtual networks in the specified site
Raises:
requests.exceptions.RequestException: If the API request fails
"""
url = f"https://{self.client.zvm_address}/v1/virtualizationsites/{site_identifier}/publiccloud/virtualNetworks"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
logging.info(f"VirtualizationSites.get_virtualization_site_public_cloud_networks: Fetching public cloud virtual networks for site {site_identifier}...")
try:
response = requests.get(url, headers=headers, 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 get_virtualization_site_public_cloud_subnets(self, site_identifier):
"""
Get a list of public cloud subnets from the specified site. (Auth)
Args:
site_identifier (str): The identifier of the site to get public cloud subnets from.
Endpoint:
/v1/virtualizationsites/{siteIdentifier}/publiccloud/subnets
Returns:
list: Array of public cloud subnets in the specified site
Raises:
requests.exceptions.RequestException: If the API request fails
"""
url = f"https://{self.client.zvm_address}/v1/virtualizationsites/{site_identifier}/publiccloud/subnets"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
logging.info(f"VirtualizationSites.get_virtualization_site_public_cloud_subnets: Fetching public cloud subnets for site {site_identifier}...")
try:
response = requests.get(url, headers=headers, 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 get_virtualization_site_public_cloud_security_groups(self, site_identifier):
"""
Get a list of public cloud security groups from the specified site. (Auth)
Args:
site_identifier (str): The identifier of the site to get public cloud security groups from.
Endpoint:
/v1/virtualizationsites/{siteIdentifier}/publiccloud/securityGroups
Returns:
list: Array of public cloud security groups in the specified site
Raises:
requests.exceptions.RequestException: If the API request fails
"""
url = f"https://{self.client.zvm_address}/v1/virtualizationsites/{site_identifier}/publiccloud/securityGroups"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
logging.info(f"VirtualizationSites.get_virtualization_site_public_cloud_security_groups: Fetching public cloud security groups for site {site_identifier}...")
try:
response = requests.get(url, headers=headers, 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 get_virtualization_site_public_cloud_vm_instance_types(self, site_identifier):
"""
Get a list of public cloud VM instance types from the specified site. (Auth)
Args:
site_identifier (str): The identifier of the site to get VM instance types from.
Endpoint:
/v1/virtualizationsites/{siteIdentifier}/publiccloud/vmInstanceTypes
Returns:
list: Array of public cloud VM instance types in the specified site
Raises:
requests.exceptions.RequestException: If the API request fails
"""
url = f"https://{self.client.zvm_address}/v1/virtualizationsites/{site_identifier}/publiccloud/vmInstanceTypes"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
logging.info(f"VirtualizationSites.get_virtualization_site_public_cloud_vm_instance_types: Fetching VM instance types for site {site_identifier}...")
try:
response = requests.get(url, headers=headers, 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 get_virtualization_site_public_cloud_resource_groups(self, site_identifier):
"""
Get a list of public cloud resource groups from the specified site. (Auth)
Args:
site_identifier (str): The identifier of the site to get resource groups from.
Endpoint:
/v1/virtualizationsites/{siteIdentifier}/publiccloud/resourceGroups
Returns:
list: Array of public cloud resource groups in the specified site
Raises:
requests.exceptions.RequestException: If the API request fails
"""
url = f"https://{self.client.zvm_address}/v1/virtualizationsites/{site_identifier}/publiccloud/resourceGroups"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
logging.info(f"VirtualizationSites.get_virtualization_site_public_cloud_resource_groups: Fetching resource groups for site {site_identifier}...")
try:
response = requests.get(url, headers=headers, 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 get_virtualization_site_public_cloud_keys_containers(self, site_identifier):
"""
Get a list of public cloud keys containers from the specified site. (Auth)
Args:
site_identifier (str): The identifier of the site to get keys containers from.
Endpoint:
/v1/virtualizationsites/{siteIdentifier}/publiccloud/keyscontainers
Returns:
list: Array of public cloud keys containers in the specified site
Raises:
requests.exceptions.RequestException: If the API request fails
"""
url = f"https://{self.client.zvm_address}/v1/virtualizationsites/{site_identifier}/publiccloud/keyscontainers"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
logging.info(f"VirtualizationSites.get_virtualization_site_public_cloud_keys_containers: Fetching keys containers for site {site_identifier}...")
try:
response = requests.get(url, headers=headers, 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 get_virtualization_site_public_cloud_encryption_keys(self, site_identifier, encryption_key_id=None):
"""
Get public cloud encryption keys from the specified site. (Auth)
Args:
site_identifier (str): The identifier of the site.
encryption_key_id (str, optional): The identifier of a specific encryption key to retrieve.
If not provided, returns all encryption keys.
Endpoints:
- /v1/virtualizationsites/{siteIdentifier}/publiccloud/encryptionkeys (when encryption_key_id is None)
- /v1/virtualizationsites/{siteIdentifier}/publiccloud/encryptionkeys/{encryptionKeyId} (when encryption_key_id is provided)
Returns:
list or dict: Array of encryption keys if encryption_key_id is None, otherwise details of specific key
Raises:
requests.exceptions.RequestException: If the API request fails
"""
url = f"https://{self.client.zvm_address}/v1/virtualizationsites/{site_identifier}/publiccloud/encryptionkeys"
if encryption_key_id:
url = f"{url}/{encryption_key_id}"
logging.info(f"VirtualizationSites.get_virtualization_site_public_cloud_encryption_keys: Fetching encryption key {encryption_key_id} for site {site_identifier}...")
else:
logging.info(f"VirtualizationSites.get_virtualization_site_public_cloud_encryption_keys: Fetching all encryption keys for site {site_identifier}...")
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
try:
response = requests.get(url, headers=headers, 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 get_virtualization_site_public_cloud_managed_identities(self, site_identifier):
"""
Get a list of public cloud managed identities from the specified site. (Auth)
Args:
site_identifier (str): The identifier of the site to get managed identities from.
Endpoint:
/v1/virtualizationsites/{siteIdentifier}/publiccloud/managedidentities
Returns:
list: Array of public cloud managed identities in the specified site
Raises:
requests.exceptions.RequestException: If the API request fails
"""
url = f"https://{self.client.zvm_address}/v1/virtualizationsites/{site_identifier}/publiccloud/managedidentities"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
logging.info(f"VirtualizationSites.get_virtualization_site_public_cloud_managed_identities: Fetching managed identities for site {site_identifier}...")
try:
response = requests.get(url, headers=headers, 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 get_virtualization_site_public_cloud_disk_encryption_keys(self, site_identifier):
"""
Get a list of public cloud disk encryption keys from the specified site. (Auth)
Args:
site_identifier (str): The identifier of the site to get disk encryption keys from.
Endpoint:
/v1/virtualizationsites/{siteIdentifier}/publiccloud/diskencryptionkeys
Returns:
list: Array of public cloud disk encryption keys in the specified site
Raises:
requests.exceptions.RequestException: If the API request fails
"""
url = f"https://{self.client.zvm_address}/v1/virtualizationsites/{site_identifier}/publiccloud/diskencryptionkeys"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
logging.info(f"VirtualizationSites.get_virtualization_site_public_cloud_disk_encryption_keys: Fetching disk encryption keys for site {site_identifier}...")
try:
response = requests.get(url, headers=headers, 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
+349
View File
@@ -0,0 +1,349 @@
# 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
+59
View File
@@ -0,0 +1,59 @@
import requests
import logging
import json
class Volumes:
def __init__(self, client):
self.client = client
def list_volumes(self, volume_type=None, vpg_identifier=None, datastore_identifier=None,
protected_vm_identifier=None, owning_vm_identifier=None):
"""
Get a list of volumes info in the current site. For ZSSP users, the information
retrieved is for Protected entities only. (Auth)
Args:
volume_type (str, optional): The volume type
vpg_identifier (str, optional): The identifier of the VPG
datastore_identifier (str, optional): The identifier of the datastore
protected_vm_identifier (str, optional): The identifier of the protected virtual machine
owning_vm_identifier (str, optional): The identifier of the owning virtual machine
Returns:
list: Array of volume information objects
Raises:
requests.exceptions.RequestException: If the API request fails
"""
url = f"https://{self.client.zvm_address}/v1/volumes"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
params = {
'volumeType': volume_type,
'vpgIdentifier': vpg_identifier,
'datastoreIdentifier': datastore_identifier,
'protectedVmIdentifier': protected_vm_identifier,
'owningVmIdentifier': owning_vm_identifier
}
# Remove None values from params
params = {k: v for k, v in params.items() if v is not None}
logging.info("Volumes.list_volumes: Fetching volumes information")
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
+995
View File
@@ -0,0 +1,995 @@
# 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 time
import json
from .tasks import Tasks
from .common import ZertoVPGStatus, ZertoVPGSubstatus, ZertoProtectedSiteType, ZertoRecoverySiteType, ZertoVPGPriority
from typing import Optional, Union, Dict, List
class VPGs:
def __init__(self, client):
self.client = client
self.tasks = Tasks(client)
def list_vpgs(self,
vpg_name: str = None,
vpg_identifier: str = None,
status: ZertoVPGStatus = None,
sub_status: ZertoVPGSubstatus = None,
protected_site_type: ZertoProtectedSiteType = None,
recovery_site_type: ZertoRecoverySiteType = None,
protected_site_identifier: str = None,
recovery_site_identifier: str = None,
organization_name: str = None,
zorg_identifier: str = None,
priority: ZertoVPGPriority = None,
service_profile_identifier: str = None,
backup_enabled: bool = None) -> Dict | List[Dict]:
"""
Get information about VPGs. If vpg_identifier or vpg_name is provided, returns a single VPG.
Otherwise, returns a list of VPGs that match the filter criteria.
Args:
vpg_name: Get a specific VPG by name
vpg_identifier: Get a specific VPG by identifier
status: Filter by VPG status
sub_status: Filter by VPG sub-status
protected_site_type: The protected site type
recovery_site_type: The recovery site type
protected_site_identifier: The identifier of the protected site
recovery_site_identifier: The identifier of the recovery site
organization_name: Filter by ZORG name
zorg_identifier: Filter by ZORG identifier
priority: Filter by VPG priority
service_profile_identifier: Filter by service profile ID
backup_enabled: Deprecated parameter
Returns:
Dict: When vpg_identifier or vpg_name is provided
List[Dict]: When filtering VPGs without specific identifier
"""
# Construct the base URL
if vpg_identifier:
url = f"https://{self.client.zvm_address}/v1/vpgs/{vpg_identifier}"
else:
url = f"https://{self.client.zvm_address}/v1/vpgs"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
# Only include query parameters if we're not getting a specific VPG
params = {}
if not vpg_identifier:
params = {
'name': vpg_name,
'status': status.get_name_by_value(status.value) if status else None,
'subStatus': sub_status.get_name_by_value(sub_status.value) if sub_status else None,
'protectedSiteType': protected_site_type.get_name_by_value(protected_site_type.value) if protected_site_type else None,
'recoverySiteType': recovery_site_type.get_name_by_value(recovery_site_type.value) if recovery_site_type else None,
'protectedSiteIdentifier': protected_site_identifier,
'recoverySiteIdentifier': recovery_site_identifier,
'organizationName': organization_name,
'zorgIdentifier': zorg_identifier,
'priority': priority.get_name_by_value(priority.value) if priority else None,
'serviceProfileIdentifier': service_profile_identifier,
'backupEnabled': backup_enabled
}
# Remove None values from params
params = {k: v for k, v in params.items() if v is not None}
logging.info(f"VPGs.list_vpgs: Fetching VPGs with parameters:")
if vpg_identifier:
logging.info(f" vpg_identifier: {vpg_identifier}")
for key, value in params.items():
logging.info(f" {key}: {value}")
try:
response = requests.get(
url,
headers=headers,
params=params,
verify=self.client.verify_certificate,
timeout=30
)
response.raise_for_status()
result = response.json()
# If we're querying by name, return the first matching VPG
if vpg_name and isinstance(result, list):
matching_vpg = next((vpg for vpg in result if vpg.get("VpgName") == vpg_name), None)
if matching_vpg:
logging.info(f"Successfully retrieved VPG details for {vpg_name}")
return matching_vpg
logging.warning(f"No VPG found with name {vpg_name}")
return {}
if vpg_identifier:
logging.info(f"Successfully retrieved VPG details for {vpg_identifier}")
else:
logging.info(f"Successfully retrieved {len(result)} VPGs")
return result
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 commit_vpg(self, vpg_settings_id, vpg_name, sync=False, expected_status=ZertoVPGStatus.Initializing, timeout=30, interval=5):
logging.info(f'VPGs.commit_vpg(zvm_address={self.client.zvm_address}, vpg_settings_id={vpg_settings_id}, vpg_name={vpg_name}, sync={sync})')
commit_uri = f"https://{self.client.zvm_address}/v1/vpgSettings/{vpg_settings_id}/commit"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
try:
response = requests.post(commit_uri, headers=headers, verify=self.client.verify_certificate)
response.raise_for_status()
task_id = response.json()
logging.info(f"VPGSettings {vpg_settings_id} successfully committed, {vpg_name} is created, task_id={task_id}")
if sync:
# Wait for task completion
self.tasks.wait_for_task_completion(task_id, timeout=timeout, interval=interval)
logging.debug('sleeping 5 seconds ...')
self.wait_for_vpg_ready(vpg_name=vpg_name, timeout=30, interval=5, expected_status=expected_status)
return task_id
return task_id
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 create_vpg(self, basic, journal, recovery, networks, sync=True, status: ZertoVPGStatus = ZertoVPGStatus.Initializing, timeout=30, interval=5):
vpg_name = basic.get("Name")
logging.info(f'VPGs.create_vpg(zvm_address={self.client.zvm_address}, vpg_name={vpg_name}, sync={sync})')
vpg_settings_id = self.create_vpg_settings(basic, journal, recovery, networks, vpg_identifier=None)
return self.commit_vpg(vpg_settings_id, vpg_name, sync, expected_status=status, timeout=timeout, interval=interval)
def wait_for_vpg_ready(self, vpg_name, timeout=180, interval=5, expected_status=ZertoVPGStatus.Initializing):
logging.debug(f'VPGs.wait_for_vpg_ready(zvm_address={self.client.zvm_address}, vpg_name={vpg_name}, timeout={timeout}, interval={interval}, expected_status={ZertoVPGStatus.get_name_by_value(expected_status.value)})')
start_time = time.time()
while True:
time.sleep(interval)
vpg_info = self.list_vpgs(vpg_name=vpg_name)
# get status and convert string into enum
logging.debug(f"VPG status: {vpg_info.get('Status')}")
vpg_status: ZertoVPGStatus = ZertoVPGStatus(vpg_info.get("Status"))
logging.debug(f"Checking VPG status for {vpg_name}: Expected status = {ZertoVPGStatus.get_name_by_value(expected_status.value)}, Current status = {ZertoVPGStatus.get_name_by_value(vpg_status.value)}")
# If VPG is in the expected status or passed the Initializing status too quickly and is in another status
if vpg_status == expected_status or (expected_status == ZertoVPGStatus.Initializing and vpg_status.value > ZertoVPGStatus.Initializing.value):
logging.info(f"VPG {vpg_name} is now in the expected state: {ZertoVPGStatus.get_name_by_value(vpg_status.value)}")
return vpg_info
# Check if the timeout has been reached
elapsed_time = time.time() - start_time
if elapsed_time > timeout:
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(self, vpg_name, vm_list_payload):
logging.info(f'VPGs.add_vm_to_vpg(zvm_address={self.client.zvm_address}, vpg_name={vpg_name})')
vpg = self.list_vpgs(vpg_name=vpg_name)
if not vpg:
logging.error(f"VPG with name '{vpg_name}' not found.")
return
vpg_identifier = vpg['VpgIdentifier']
logging.info(f"Found VPG '{vpg_name}' with Identifier: {vpg_identifier}")
new_vpg_settings_id = self.create_vpg_settings(basic=None, journal=None, recovery=None, networks=None, vpg_identifier=vpg_identifier)
logging.info(f"Adding VMs to VPGSettings ID: {new_vpg_settings_id}")
logging.debug(f"VM List Payload: {json.dumps(vm_list_payload, indent=4)}")
vms_uri = f"https://{self.client.zvm_address}/v1/vpgSettings/{new_vpg_settings_id}/vms"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
try:
response = requests.post(vms_uri, headers=headers, json=vm_list_payload, verify=self.client.verify_certificate)
response.raise_for_status()
logging.info(f"Successfully added VMs to VPG {new_vpg_settings_id}.")
self.commit_vpg(new_vpg_settings_id, vpg_name, sync=True, expected_status=ZertoVPGStatus.Initializing)
return
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 remove_vm_from_vpg(self, vpg_name, vm_identifier):
logging.info(f'VPGs.remove_vm_from_vpg(zvm_address={self.client.zvm_address}, vpg_name={vpg_name}, vm_identifier={vm_identifier})')
vpg = self.list_vpgs(vpg_name=vpg_name)
if not vpg:
logging.error(f"VPG with name '{vpg_name}' not found.")
return
vpg_id = vpg['VpgIdentifier']
logging.info(f"Found VPG '{vpg_name}' with Identifier: {vpg_id}")
new_vpg_settings_id = self.create_vpg_settings(basic=None, journal=None, recovery=None, networks=None, vpg_identifier=vpg_id)
remove_vm_uri = f"https://{self.client.zvm_address}/v1/vpgSettings/{new_vpg_settings_id}/vms/{vm_identifier}"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
try:
response = requests.delete(remove_vm_uri, headers=headers, verify=self.client.verify_certificate)
response.raise_for_status()
logging.info(f"VM {vm_identifier} successfully removed from VPG '{vpg_name}' (ID: {new_vpg_settings_id}).")
self.commit_vpg(new_vpg_settings_id, vpg_name, sync=True, expected_status=ZertoVPGStatus.Initializing)
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 failover_test(self, vpg_name, checkpoint_identifier=None, vm_name_list=None, sync=True):
"""
Initiate a failover test for a given VPG by its name.
:param vpg_name: The name of the VPG.
:param checkpoint_identifier: checkpoint_identifier can be recived by list_checkpoint, if not provided uses the latest checkpoint.
:param vm_name_list: List of
:param options: Optional parameters for the failover test.
:return: Response from the Zerto API.
"""
logging.info(f'VPGs.failover_test(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}/FailoverTest"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
payload = {}
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_test vm={vm} not found')
return
vm_identifier_list.append(vm_info[0]['VmIdentifier'])
payload['VmIdentifiers'] = vm_identifier_list
try:
logging.info(f"Initiating failover test 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 test 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 stop_failover_test(self, vpg_name, failoverTestSuccess=True, failoverTestSummary=None, sync=True):
"""
Stop a failover test 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.stop_failover_test(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}")
url = f"https://{self.client.zvm_address}/v1/vpgs/{vpg_identifier}/FailoverTestStop"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
body = {
"FailoverTestSuccess": failoverTestSuccess,
"FailoverTestSummary": failoverTestSummary
}
try:
logging.info(f"Stopping failover test for VPG '{vpg_name}'...")
response = requests.post(url, headers=headers, json=body, verify=self.client.verify_certificate)
response.raise_for_status()
task_id = response.json()
logging.info(f"Failover test stopping 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.
:param vpg_name: The name of the VPG.
:param sync: wait until task is completed.
"""
logging.info(f'VPGs.rollback_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}")
url = f"https://{self.client.zvm_address}/v1/vpgs/{vpg_identifier}/FailoverRollback"
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, verify=self.client.verify_certificate)
response.raise_for_status()
task_id = response.json()
logging.info(f"Rollback faolover 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.
:param vpg_name: The name of the VPG to delete.
:return: Success message if deleted, else an error message.
"""
logging.info(f"VPGs.delete_vpg(zvm_address={self.client.zvm_address}, vpg_name={vpg_name}, force={force}, keep_recovery_volumes={keep_recovery_volumes})")
# Step 1: Retrieve the VPG details using the VPG name
vpg = self.list_vpgs(vpg_name=vpg_name)
if not vpg:
logging.error(f"No VPG found with the name '{vpg_name}'.")
return
# Get the VPG Identifier
vpg_identifier = vpg.get("VpgIdentifier")
if not vpg_identifier:
logging.error(f"Could not retrieve Identifier for VPG '{vpg_name}'.")
return
# Step 2: Construct the DELETE request URL
delete_vpg_uri = f"https://{self.client.zvm_address}/v1/vpgs/{vpg_identifier}"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
payload = {
"keepRecoveryVolumes": force,
"force": keep_recovery_volumes
}
try:
# Step 3: Send DELETE request
response = requests.delete(delete_vpg_uri, headers=headers, json=payload, verify=self.client.verify_certificate)
response.raise_for_status() # Ensure the request was successful
logging.info(f"Successfully deleted VPG '{vpg_name}' (ID: {vpg_identifier}).")
return f"VPG '{vpg_name}' deleted successfully."
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
# Added methods from VPGSettings
def list_vpg_settings(self):
url = f"https://{self.client.zvm_address}/v1/vpgs/settings"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
try:
response = requests.get(url, headers=headers, verify=self.client.verify_certificate)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
logging.error(f"Failed to list VPG settings: {e}")
raise
def get_vpg_settings_by_id(self, vpg_settings_id):
url = f"https://{self.client.zvm_address}/v1/vpgSettings/{vpg_settings_id}"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
try:
response = requests.get(url, headers=headers, verify=self.client.verify_certificate)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
logging.error(f"Failed to get VPG settings by ID: {e}")
raise
def update_vpg_settings(self, vpg_settings_id, payload):
url = f"https://{self.client.zvm_address}/v1/vpgSettings/{vpg_settings_id}"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
logging.info(f"VPGs.update_vpg_settings: Updating VPG settings for ID: {vpg_settings_id}")
logging.debug(f"VPGs.update_vpg_settings: Payload: {json.dumps(payload, indent=4)}")
try:
response = requests.put(url, json=payload, headers=headers, verify=self.client.verify_certificate)
response.raise_for_status()
return response
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 delete_vpg_settings(self, vpg_settings_id):
url = f"https://{self.client.zvm_address}/v1/vpgs/settings/{vpg_settings_id}"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
try:
response = requests.delete(url, headers=headers, 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 create_vpg_settings(self, basic, journal, recovery, networks, vpg_identifier=None):
logging.info(f'VPGs.create_vpg_settings(zvm_address={self.client.zvm_address}, vpg_identifier={vpg_identifier})')
vpg_settings_uri = f"https://{self.client.zvm_address}/v1/vpgSettings"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
payload = {}
if vpg_identifier:
payload["vpgIdentifier"] = vpg_identifier
if basic:
payload["Basic"] = basic
if journal:
payload["Journal"] = journal
if recovery:
payload["Recovery"] = recovery
if networks:
payload["Networks"] = networks
logging.debug(f"VPGs.create_vpg_settings: Payload: {json.dumps(payload, indent=4)}")
try:
response = requests.post(vpg_settings_uri, headers=headers, json=payload, verify=self.client.verify_certificate)
response.raise_for_status()
vpg_settings_id = response.json()
logging.info(f"VPG Settings ID: {vpg_settings_id} created")
return vpg_settings_id
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 list_checkpoints(self, vpg_name, start_date=None, endd_date=None, checkpoint_date_str=None, latest=None):
"""
Fetches a list of checkpoints for a specified Virtual Protection Group (VPG).
Parameters:
vpg_name (str): The name of the Virtual Protection Group (VPG) to fetch checkpoints for.
start_date (str): The start date for filtering checkpoints, in ISO 8601 format (e.g., '2024-11-13T00:00:00Z').
endd_date (str): The end date for filtering checkpoints, in ISO 8601 format (e.g., '2024-11-14T00:00:00Z').
checkpoint_date_str (str): A specific date string in the format 'Month Day, Year HH:MM:SS AM/PM'
(e.g., 'November 13, 2024 1:43:02 PM') to search for an exact checkpoint.
latest (bool): If True, returns the checkpoint with the most recent timestamp.
Returns:
dict: A single checkpoint that matches `checkpoint_date_str` or the latest checkpoint if `latest=True`.
list: The full list of checkpoints if neither `checkpoint_date_str` nor `latest` is specified.
None: If no matching checkpoint is found or an error occurs.
Raises:
SystemExit: If a request exception occurs during the API call.
"""
logging.info(f'VPGs.list_checkpoints(vpg_name={vpg_name}, start_date={start_date}, endd_date={endd_date}, checkpoint_date_str={checkpoint_date_str}, latest={latest})')
vpgid = (self.list_vpgs(vpg_name=vpg_name))['VpgIdentifier']
vpgs_uri = f"https://{self.client.zvm_address}/v1/vpgs/{vpgid}/checkpoints"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
params = {
"startDate": start_date,
"endDate": endd_date
}
try:
response = requests.get(vpgs_uri, headers=headers, params=params, verify=self.client.verify_certificate)
response.raise_for_status()
checkpoints = response.json()
if not checkpoints:
logging.warning("No checkpoints found.")
return []
if checkpoint_date_str:
check_point_timestamp = self.__convert_datetime_to_timestamp(date_str = checkpoint_date_str)
matching_checkpoints = next((checkpoint for checkpoint in checkpoints if checkpoint.get("TimeStamp") == check_point_timestamp), None)
if not check_point_timestamp:
logging.warning(f"No checkpoint {checkpoint_date_str} found")
return {}
return matching_checkpoints
if latest:
# Find the checkpoint with the most recent timestamp
latest_checkpoint = max(checkpoints, key=lambda x: x.get("TimeStamp"))
logging.debug(f"Latest checkpoint found: {latest_checkpoint}")
return latest_checkpoint
return checkpoints
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 create_checkpoint(self, checkpoint_name: str, vpg_identifier: str = None, vpg_name: str = None) -> str:
"""
Create a tagged checkpoint for the VPG.
Args:
checkpoint_name: The name/tag to assign to the checkpoint
vpg_identifier: The identifier of the VPG
vpg_name: The name of the VPG (alternative to vpg_identifier)
Returns:
str: The task identifier that can be used to monitor the operation
Raises:
requests.exceptions.RequestException: If the API request fails
ValueError: If neither vpg_identifier nor vpg_name is provided, or if VPG name is not found
"""
if not vpg_identifier and not vpg_name:
raise ValueError("Either vpg_identifier or vpg_name must be provided")
# If vpg_name is provided, get the vpg_identifier
if vpg_name and not vpg_identifier:
vpg = self.list_vpgs(vpg_name=vpg_name)
if not vpg:
raise ValueError(f"VPG with name '{vpg_name}' not found")
vpg_identifier = vpg.get('VpgIdentifier')
logging.info(f"Found VPG identifier '{vpg_identifier}' for VPG name '{vpg_name}'")
url = f"https://{self.client.zvm_address}/v1/vpgs/{vpg_identifier}/checkpoints"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
data = {
"CheckpointName": checkpoint_name
}
logging.info(f"VPGs.create_checkpoint: Creating checkpoint '{checkpoint_name}' for VPG {vpg_identifier}")
try:
response = requests.post(
url,
headers=headers,
json=data,
verify=self.client.verify_certificate,
timeout=30
)
response.raise_for_status()
task_id = response.json()
logging.info(f"Successfully initiated checkpoint creation, task_id={task_id}")
return task_id
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 export_vpg_settings(self, vpg_names: List[str]) -> dict:
"""
Export settings for specified VPGs.
Args:
vpg_names: List of VPG names to export settings for
Returns:
dict: The exported VPG settings in the format:
{
"timeStamp": "2025-02-08T21:50:46.574Z",
"exportResult": {
"result": str,
"message": str
}
}
Raises:
requests.exceptions.RequestException: If the API request fails
"""
url = f"https://{self.client.zvm_address}/v1/vpgSettings/exportSettings"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
payload = {
"vpgNames": vpg_names
}
logging.info(f"VPGs.export_vpg_settings: Exporting settings for VPGs: {vpg_names}")
try:
response = requests.post(url, headers=headers, json=payload, verify=self.client.verify_certificate)
response.raise_for_status()
result = response.json()
logging.info(f"Successfully exported settings for {len(vpg_names)} VPGs at {result.get('timeStamp')}")
logging.debug(f"Export result: {json.dumps(result, indent=2)}")
return result
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_exported_vpg_settings(self) -> List[Dict]:
"""
Get all available exported settings files.
Returns:
List[Dict]: List of exported settings files in the format:
[
{
"timeStamp": "2025-02-08T22:02:18.685Z"
}
]
Raises:
requests.exceptions.RequestException: If the API request fails
"""
url = f"https://{self.client.zvm_address}/v1/vpgSettings/exportedSettings"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
logging.debug("Fetching list of exported VPG settings")
try:
response = requests.get(url, headers=headers, verify=self.client.verify_certificate)
response.raise_for_status()
result = response.json()
logging.info(f"Found {len(result)} exported settings files")
logging.debug(f"Exported settings list: {json.dumps(result, indent=2)}")
return result
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 read_exported_vpg_settings(self, timestamp: str, vpg_names: List[str] = None) -> dict:
"""
Read exported settings from a file of given timestamp.
Args:
timestamp: The timestamp of the exported settings file (format: YYYY-MM-DDThh:mm:ss.SSSZ)
vpg_names: Optional list of VPG names to filter the exported settings
Returns:
dict: The exported VPG settings containing:
- ExportedVpgSettingsApi: List[dict] - List of VPG settings
- ErrorMessage: str - Error message if any
Raises:
requests.exceptions.RequestException: If the API request fails
"""
url = f"https://{self.client.zvm_address}/v1/vpgSettings/exportedSettings/{timestamp}"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
payload = {}
if vpg_names:
payload['vpgNames'] = vpg_names
logging.info(f"VPGs.read_exported_vpg_settings: Reading exported VPG settings for timestamp: {timestamp}")
if vpg_names:
logging.debug(f"Filtering for VPGs: {vpg_names}")
try:
response = requests.post(url, headers=headers, json=payload, verify=self.client.verify_certificate)
response.raise_for_status()
result = response.json()
logging.debug(f"VPGs.read_exported_vpg_settings: result: {json.dumps(result, indent=4)}")
return result
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 import_vpg_settings(self, settings: Dict) -> dict:
"""
Import VPG settings.
Args:
settings: Dictionary containing the VPG settings to import. Must include:
- ExportedVpgSettingsApi: List of VPG settings with detailed configuration
Returns:
dict: The import result containing:
- validationFailedResults: List[dict] - VPGs that failed validation
- vpgName: str - Name of the VPG
- errorMessages: List[str] - List of validation error messages
- importFailedResults: List[dict] - VPGs that failed to import
- vpgName: str - Name of the VPG
- errorMessage: str - Import error message
- importTaskIdentifiers: List[dict] - Successfully initiated imports
- vpgName: str - Name of the VPG
- taskIdentifier: str - Task ID for tracking the import
Raises:
requests.exceptions.RequestException: If the API request fails
ValueError: If settings dictionary is missing required fields
"""
url = f"https://{self.client.zvm_address}/v1/vpgSettings/import"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
# Validate input settings
if not isinstance(settings, dict):
raise ValueError("Settings must be a dictionary")
if 'ExportedVpgSettingsApi' not in settings:
raise ValueError("Settings must contain 'ExportedVpgSettingsApi' key")
# Prepare payload
payload = {
"ExportedVpgSettingsApi": settings['ExportedVpgSettingsApi']
}
logging.info(f"VPGs.import_vpg_settings: Importing settings for {len(settings['ExportedVpgSettingsApi'])} VPGs")
try:
response = requests.post(url, headers=headers, json=payload, verify=self.client.verify_certificate)
response.raise_for_status()
result = response.json()
logging.debug(f"VPGs.import_vpg_settings: result: {json.dumps(result, indent=4)}")
# Log validation failures
if result.get('validationFailedResults'):
for failure in result['validationFailedResults']:
logging.error(f"Validation failed for VPG '{failure['vpgName']}': {', '.join(failure['errorMessages'])}")
# Log import failures
if result.get('importFailedResults'):
for failure in result['importFailedResults']:
logging.error(f"Import failed for VPG '{failure['vpgName']}': {failure['errorMessage']}")
# Log successful imports
if result.get('importTaskIdentifiers'):
for task in result['importTaskIdentifiers']:
logging.info(f"Import initiated for VPG '{task['vpgName']}' with task ID: {task['taskIdentifier']}")
logging.debug(f"Import result: {json.dumps(result, indent=2)}")
return result
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
+668
View File
@@ -0,0 +1,668 @@
# 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
from typing import Dict, List, Optional
class VRA:
def __init__(self, client):
self.client = client
def list_vras(self) -> List[Dict]:
"""List all VRAs."""
logging.info(f"VRA.list_vras(zvm_address={self.client.zvm_address})")
url = f"https://{self.client.zvm_address}/v1/vras"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
try:
response = requests.get(url, headers=headers, verify=self.client.verify_certificate)
response.raise_for_status()
result = response.json()
logging.info(f"Successfully retrieved {len(result)} VRAs")
logging.debug(f"VRA.list_vras result: {json.dumps(result, indent=4)}")
return result
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 create_vra(self, payload: Dict, sync: bool = True) -> Dict:
"""Create a new VRA.
Args:
payload: The VRA configuration
sync: If True, wait for task completion (default: True)
Returns:
Dict: The creation result
Raises:
requests.exceptions.RequestException: If the API request fails
"""
logging.info(f"VRA.create_vra(zvm_address={self.client.zvm_address}, sync={sync})")
logging.debug(f"VRA.create_vra payload: {json.dumps(payload, indent=4)}")
url = f"https://{self.client.zvm_address}/v1/vras"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
try:
response = requests.post(url, headers=headers, json=payload, verify=self.client.verify_certificate)
response.raise_for_status()
task_id = response.json()
logging.info("Successfully initiated VRA creation")
logging.debug(f"VRA.create_vra task_id: {task_id}")
if sync:
# Wait for task completion
self.client.tasks.wait_for_task_completion(task_id, timeout=300, interval=5)
return task_id
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 get_vra(self, vra_identifier: str) -> Dict:
"""
Get information about a specific VRA.
Args:
vra_identifier: The identifier of the VRA to retrieve
Returns:
Dict: The VRA information
Raises:
requests.exceptions.RequestException: If the API request fails
"""
logging.info(f"VRA.get_vra(zvm_address={self.client.zvm_address}, vra_identifier={vra_identifier})")
url = f"https://{self.client.zvm_address}/v1/vras/{vra_identifier}"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
try:
response = requests.get(url, headers=headers, verify=self.client.verify_certificate)
response.raise_for_status()
result = response.json()
logging.info(f"Successfully retrieved VRA information for identifier: {vra_identifier}")
logging.debug(f"VRA.get_vra result: {json.dumps(result, indent=4)}")
return result
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 delete_vra(self, vra_identifier: str, sync: bool = True) -> Dict:
"""
Delete a specific VRA.
Args:
vra_identifier: The identifier of the VRA to delete
sync: If True, wait for task completion (default: True)
Returns:
Dict: The deletion result
Raises:
requests.exceptions.RequestException: If the API request fails
"""
logging.info(f"VRA.delete_vra(zvm_address={self.client.zvm_address}, vra_identifier={vra_identifier})")
url = f"https://{self.client.zvm_address}/v1/vras/{vra_identifier}"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
try:
response = requests.delete(url, headers=headers, verify=self.client.verify_certificate)
response.raise_for_status()
task_id = response.json()
logging.info(f"Successfully initiated deletion of VRA with identifier: {vra_identifier}")
logging.debug(f"VRA.delete_vra task_id: {task_id}")
if sync:
# Wait for task completion
self.client.tasks.wait_for_task_completion(task_id, timeout=300, interval=5)
return task_id
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 update_vra(self, vra_identifier: str, payload: Dict, sync: bool = True) -> Dict:
"""
Update a specific VRA's configuration.
Args:
vra_identifier: The identifier of the VRA to update
payload: The update configuration
sync: If True, wait for task completion (default: True)
Returns:
Dict: The update result
Raises:
requests.exceptions.RequestException: If the API request fails
"""
logging.info(f"VRA.update_vra(zvm_address={self.client.zvm_address}, vra_identifier={vra_identifier})")
logging.debug(f"VRA.update_vra payload: {json.dumps(payload, indent=4)}")
url = f"https://{self.client.zvm_address}/v1/vras/{vra_identifier}"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
try:
response = requests.put(url, headers=headers, json=payload, verify=self.client.verify_certificate)
response.raise_for_status()
task_id = response.json()
logging.info(f"Successfully initiated update for VRA with identifier: {vra_identifier}")
logging.debug(f"VRA.update_vra task_id: {task_id}")
if sync:
# Wait for task completion
self.client.tasks.wait_for_task_completion(task_id, timeout=300, interval=5)
return task_id
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 create_vra_cluster(self, payload: Dict, sync: bool = True) -> Dict:
"""
Create a new VRA cluster.
Args:
payload: The cluster configuration
sync: If True, wait for task completion (default: True)
Returns:
Dict: The creation result
Raises:
requests.exceptions.RequestException: If the API request fails
"""
logging.info(f"VRA.create_vra_cluster(zvm_address={self.client.zvm_address})")
logging.debug(f"VRA.create_vra_cluster payload: {json.dumps(payload, indent=4)}")
url = f"https://{self.client.zvm_address}/v1/vras/clusters"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
try:
response = requests.post(url, headers=headers, json=payload, verify=self.client.verify_certificate)
response.raise_for_status()
task_id = response.json()
logging.info("Successfully initiated VRA cluster creation")
logging.debug(f"VRA.create_vra_cluster task_id: {task_id}")
if sync:
# Wait for task completion
self.client.tasks.wait_for_task_completion(task_id, timeout=300, interval=5)
return task_id
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 delete_vra_cluster(self, cluster_identifier: str) -> Dict:
"""
Delete a VRA cluster.
Args:
cluster_identifier: The identifier of the cluster to delete
Returns:
Dict: The deletion result
Raises:
requests.exceptions.RequestException: If the API request fails
"""
logging.info(f"VRA.delete_vra_cluster(zvm_address={self.client.zvm_address}, cluster_identifier={cluster_identifier})")
url = f"https://{self.client.zvm_address}/v1/vras/clusters/{cluster_identifier}"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
try:
response = requests.delete(url, headers=headers, verify=self.client.verify_certificate)
response.raise_for_status()
result = response.json()
logging.info(f"Successfully deleted VRA cluster with identifier: {cluster_identifier}")
logging.debug(f"VRA.delete_vra_cluster result: {json.dumps(result, indent=4)}")
return result
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 update_vra_cluster(self, cluster_identifier: str, payload: Dict) -> Dict:
"""
Update a VRA cluster configuration.
Args:
cluster_identifier: The identifier of the cluster to update
payload: The update configuration
Returns:
Dict: The update result
Raises:
requests.exceptions.RequestException: If the API request fails
"""
logging.info(f"VRA.update_vra_cluster(zvm_address={self.client.zvm_address}, cluster_identifier={cluster_identifier})")
logging.debug(f"VRA.update_vra_cluster payload: {json.dumps(payload, indent=4)}")
url = f"https://{self.client.zvm_address}/v1/vras/clusters/{cluster_identifier}"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
try:
response = requests.put(url, headers=headers, json=payload, verify=self.client.verify_certificate)
response.raise_for_status()
result = response.json()
logging.info(f"Successfully updated VRA cluster with identifier: {cluster_identifier}")
logging.debug(f"VRA.update_vra_cluster result: {json.dumps(result, indent=4)}")
return result
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 cleanup_vras(self) -> Dict:
"""
Clean up VRAs.
Returns:
Dict: The cleanup result
Raises:
requests.exceptions.RequestException: If the API request fails
"""
logging.info(f"VRA.cleanup_vras(zvm_address={self.client.zvm_address})")
url = f"https://{self.client.zvm_address}/v1/vras/cleanup"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
try:
response = requests.delete(url, headers=headers, verify=self.client.verify_certificate)
response.raise_for_status()
result = response.json()
logging.info("Successfully cleaned up VRAs")
logging.debug(f"VRA.cleanup_vras result: {json.dumps(result, indent=4)}")
return result
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 upgrade_vra(self, vra_identifier: str) -> Dict:
"""
Upgrade a specific VRA.
Args:
vra_identifier: The identifier of the VRA to upgrade
Returns:
Dict: The upgrade result
Raises:
requests.exceptions.RequestException: If the API request fails
"""
logging.info(f"VRA.upgrade_vra(zvm_address={self.client.zvm_address}, vra_identifier={vra_identifier})")
url = f"https://{self.client.zvm_address}/v1/vras/{vra_identifier}/upgrade"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
try:
response = requests.post(url, headers=headers, verify=self.client.verify_certificate)
response.raise_for_status()
result = response.json()
logging.info(f"Successfully initiated upgrade for VRA with identifier: {vra_identifier}")
logging.debug(f"VRA.upgrade_vra result: {json.dumps(result, indent=4)}")
return result
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 get_vra_cluster_settings(self, cluster_identifier: str) -> Dict:
"""
Get settings for a specific VRA cluster.
Args:
cluster_identifier: The identifier of the cluster to get settings for
Returns:
Dict: The cluster settings
Raises:
requests.exceptions.RequestException: If the API request fails
"""
logging.info(f"VRA.get_vra_cluster_settings(zvm_address={self.client.zvm_address}, cluster_identifier={cluster_identifier})")
url = f"https://{self.client.zvm_address}/v1/vras/clusters/{cluster_identifier}/settings"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
try:
response = requests.get(url, headers=headers, verify=self.client.verify_certificate)
response.raise_for_status()
result = response.json()
logging.info(f"Successfully retrieved VRA cluster settings for identifier: {cluster_identifier}")
logging.debug(f"VRA.get_vra_cluster_settings result: {json.dumps(result, indent=4)}")
return result
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 create_vra_cluster_settings(self, cluster_identifier: str, payload: Dict) -> Dict:
"""
Create settings for a VRA cluster.
Args:
cluster_identifier: The identifier of the cluster to create settings for
payload: The cluster settings configuration
Returns:
Dict: The creation result
Raises:
requests.exceptions.RequestException: If the API request fails
"""
logging.info(f"VRA.create_vra_cluster_settings(zvm_address={self.client.zvm_address}, cluster_identifier={cluster_identifier})")
logging.debug(f"VRA.create_vra_cluster_settings payload: {json.dumps(payload, indent=4)}")
url = f"https://{self.client.zvm_address}/v1/vras/clusters/{cluster_identifier}/settings"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
try:
response = requests.post(url, headers=headers, json=payload, verify=self.client.verify_certificate)
response.raise_for_status()
result = response.json()
logging.info(f"Successfully created VRA cluster settings for identifier: {cluster_identifier}")
logging.debug(f"VRA.create_vra_cluster_settings result: {json.dumps(result, indent=4)}")
return result
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_vra_statuses(self) -> List[Dict]:
"""
List all VRA statuses.
Returns:
List[Dict]: List of VRA statuses
Raises:
requests.exceptions.RequestException: If the API request fails
"""
logging.info(f"VRA.list_vra_statuses(zvm_address={self.client.zvm_address})")
url = f"https://{self.client.zvm_address}/v1/vras/statuses"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
try:
response = requests.get(url, headers=headers, verify=self.client.verify_certificate)
response.raise_for_status()
result = response.json()
logging.info(f"Successfully retrieved VRA statuses")
logging.debug(f"VRA.list_vra_statuses result: {json.dumps(result, indent=4)}")
return result
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_ip_configuration_types(self) -> List[Dict]:
"""
List all IP configuration types.
Returns:
List[Dict]: List of IP configuration types
Raises:
requests.exceptions.RequestException: If the API request fails
"""
logging.info(f"VRA.list_ip_configuration_types(zvm_address={self.client.zvm_address})")
url = f"https://{self.client.zvm_address}/v1/vras/ipconfigurationtypes"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
try:
response = requests.get(url, headers=headers, verify=self.client.verify_certificate)
response.raise_for_status()
result = response.json()
logging.info("Successfully retrieved IP configuration types")
logging.debug(f"VRA.list_ip_configuration_types result: {json.dumps(result, indent=4)}")
return result
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_potential_recovery_vras(self, vra_identifier: str) -> List[Dict]:
"""
List potential recovery VRAs for a specific VRA.
Args:
vra_identifier: The identifier of the VRA to get potential recovery VRAs for
Returns:
List[Dict]: List of potential recovery VRAs
Raises:
requests.exceptions.RequestException: If the API request fails
"""
logging.info(f"VRA.list_potential_recovery_vras(zvm_address={self.client.zvm_address}, vra_identifier={vra_identifier})")
url = f"https://{self.client.zvm_address}/v1/vras/{vra_identifier}/changerecoveryvra/potentials"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
try:
response = requests.get(url, headers=headers, verify=self.client.verify_certificate)
response.raise_for_status()
result = response.json()
logging.info(f"Successfully retrieved potential recovery VRAs for identifier: {vra_identifier}")
logging.debug(f"VRA.list_potential_recovery_vras result: {json.dumps(result, indent=4)}")
return result
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 execute_recovery_vra_change(self, vra_identifier: str, payload: Dict) -> Dict:
"""
Execute a recovery VRA change for a specific VRA.
Args:
vra_identifier: The identifier of the VRA to change recovery VRA for
payload: The change configuration
Returns:
Dict: The execution result
Raises:
requests.exceptions.RequestException: If the API request fails
"""
logging.info(f"VRA.execute_recovery_vra_change(zvm_address={self.client.zvm_address}, vra_identifier={vra_identifier})")
logging.debug(f"VRA.execute_recovery_vra_change payload: {json.dumps(payload, indent=4)}")
url = f"https://{self.client.zvm_address}/v1/vras/{vra_identifier}/changerecoveryvra/execute"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
try:
response = requests.post(url, headers=headers, json=payload, verify=self.client.verify_certificate)
response.raise_for_status()
result = response.json()
logging.info(f"Successfully executed recovery VRA change for identifier: {vra_identifier}")
logging.debug(f"VRA.execute_recovery_vra_change result: {json.dumps(result, indent=4)}")
return result
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 validate_recovery_vra_change(self, vra_identifier, payload):
logging.info(f"VRA.validate_recovery_vra_change: Validating recovery VRA change for identifier: {vra_identifier}...")
url = f"https://{self.client.zvm_address}/v1/vras/{vra_identifier}/changerecoveryvra/validate"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
try:
response = requests.post(url, headers=headers, json=payload, verify=self.client.verify_certificate)
response.raise_for_status()
logging.info(f"VRA.validate_recovery_vra_change: Successfully validated recovery VRA change for identifier: {vra_identifier}.")
return response.json()
except requests.exceptions.RequestException as e:
logging.error(f"VRA.validate_recovery_vra_change: Failed to validate recovery VRA change for identifier {vra_identifier}: {e}")
raise
def recommend_recovery_vra_change(self, vra_identifier, payload):
logging.info(f"VRA.recommend_recovery_vra_change: Recommending recovery VRA change for identifier: {vra_identifier}...")
url = f"https://{self.client.zvm_address}/v1/vras/{vra_identifier}/changerecoveryvra/recommendation"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
try:
response = requests.post(url, headers=headers, json=payload, verify=self.client.verify_certificate)
response.raise_for_status()
logging.info(f"VRA.recommend_recovery_vra_change: Successfully recommended recovery VRA change for identifier: {vra_identifier}.")
return response.json()
except requests.exceptions.RequestException as e:
logging.error(f"VRA.recommend_recovery_vra_change: Failed to recommend recovery VRA change for identifier {vra_identifier}: {e}")
raise
+59
View File
@@ -0,0 +1,59 @@
# 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
class Zorgs:
def __init__(self, client):
self.client = client
def get_zorgs(self, zorg_identifier=None):
"""
Get ZORG information. If zorg_identifier is provided, returns details for that specific ZORG.
Args:
zorg_identifier (str, optional): The identifier of a specific ZORG
Returns:
dict/list: ZORG information. Returns a list of all ZORGs if no identifier is provided,
or details of a specific ZORG if identifier is provided.
"""
url = f"https://{self.client.zvm_address}/v1/zorgs"
if zorg_identifier:
url = f"{url}/{zorg_identifier}"
logging.info(f"Zorgs.get_zorgs: Fetching ZORG {zorg_identifier}...")
else:
logging.info("Zorgs.get_zorgs: Fetching all ZORGs...")
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.client.token}'
}
try:
response = requests.get(url, headers=headers, 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
except Exception as e:
logging.error(f"Unexpected error: {e}")
raise
+109
View File
@@ -0,0 +1,109 @@
# 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 ssl
# Configure logging with timestamp format
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
# Import all necessary classes
from .tasks import Tasks
from .vpgs import VPGs
# from .vpg_settings import VPGSettings
from .vms import VMs
from .failover import Failover
from .alerts import Alerts
from .peersites import PeerSites
from .events import Events
from .repositories import Repositories
from .sessions import Sessions
from .recoveryscripts import RecoveryScripts
from .encryptiondetection import EncryptionDetection
from .zorgs import Zorgs
from .localsite import LocalSite
from .datastores import Datastores
from .vras import VRA
from .recovery_reports import RecoveryReports
from .license import License
from .service_profiles import ServiceProfiles
from .server_date_time import ServerDateTime
from .virtualization_sites import VirtualizationSites
from .volumes import Volumes
from .tweaks import Tweaks
# Disable SSL warnings for self-signed certificates
context = ssl._create_unverified_context()
class ZVMLClient:
def __init__(self, zvm_address, client_id, client_secret, verify_certificate=True):
self.zvm_address = zvm_address
self.client_id = client_id
self.client_secret = client_secret
self.verify_certificate = verify_certificate
self.token = None
self.token_expiry = None
self.__get_keycloak_token()
self.tasks = Tasks(self)
self.vpgs = VPGs(self)
# self.vpg_settings = VPGSettings(self)
self.vms = VMs(self)
self.failover = Failover(self)
self.alerts = Alerts(self)
self.peersites = PeerSites(self)
self.events = Events(self)
self.repositories = Repositories(self)
self.sessions = Sessions(self)
self.recoveryscripts = RecoveryScripts(self)
self.zorgs = Zorgs(self)
self.encryptiondetection = EncryptionDetection(self)
self.localsite = LocalSite(self.zvm_address, self.token)
self.datastores = Datastores(self)
self.vras = VRA(self)
self.recovery_reports = RecoveryReports(self)
self.license = License(self)
self.service_profiles = ServiceProfiles(self)
self.server_date_time = ServerDateTime(self)
self.virtualization_sites = VirtualizationSites(self)
self.volumes = Volumes(self)
self.tweaks = Tweaks(self)
def __get_keycloak_token(self):
logging.debug(f'__get_keycloak_token(zvm_address={self.zvm_address})')
keycloak_uri = f"https://{self.zvm_address}/auth/realms/zerto/protocol/openid-connect/token"
headers = {'Content-Type': 'application/x-www-form-urlencoded'}
body = {
'client_id': self.client_id,
'client_secret': self.client_secret,
'grant_type': 'client_credentials',
'expires_in': 3600 # Request token expiration in seconds (e.g., 1 hour)
}
try:
logging.info("Connecting to Keycloak to get token...")
response = requests.post(keycloak_uri, headers=headers, data=body, verify=self.verify_certificate)
response.raise_for_status()
token_data = response.json()
self.token = token_data.get('access_token')
self.token_expiry = token_data.get('expires_in') # Store expiration time
logging.info(f"Successfully retrieved token.")
logging.info(f"Token expiration details:")
logging.info(f"- Expires in: {self.token_expiry} seconds")
logging.info(f"- Requested expiration: {body['expires_in']} seconds")
if self.token_expiry != body['expires_in']:
logging.warning(f"Server provided different expiration time than requested!")
return self.token
except requests.exceptions.RequestException as e:
logging.error(f"Error retrieving token: {e}")
raise