initial commit
This commit is contained in:
@@ -1,2 +1,276 @@
|
|||||||
# zvml-python-sdk
|
# zerto-python-library
|
||||||
zerto virtual manager linux python library with examples
|
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.
|
||||||
|
|
||||||
|
|
||||||
@@ -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()
|
||||||
@@ -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()
|
||||||
@@ -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()
|
||||||
@@ -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()
|
||||||
@@ -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()
|
||||||
@@ -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()
|
||||||
@@ -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()
|
||||||
@@ -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()
|
||||||
@@ -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()
|
||||||
@@ -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()
|
||||||
@@ -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__)
|
||||||
@@ -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()
|
||||||
@@ -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()
|
||||||
@@ -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()
|
||||||
@@ -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()
|
||||||
@@ -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()
|
||||||
@@ -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()
|
||||||
@@ -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()
|
||||||
@@ -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()
|
||||||
@@ -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()
|
||||||
@@ -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()
|
||||||
@@ -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()
|
||||||
@@ -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()
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
requests>=2.31.0
|
||||||
|
urllib3>=2.1.0
|
||||||
@@ -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.
Binary file not shown.
+324
@@ -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
@@ -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
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
@@ -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
|
||||||
@@ -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
@@ -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
|
||||||
|
|
||||||
@@ -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
|
||||||
@@ -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()
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
@@ -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
|
||||||
@@ -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
@@ -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
|
||||||
@@ -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
@@ -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
@@ -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
|
||||||
@@ -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
@@ -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
|
||||||
Reference in New Issue
Block a user