added examples that allows bulk vpg creating and re-ip using csv files

This commit is contained in:
Kosta Mushkin
2025-05-27 19:47:37 -04:00
parent 4cf11b7bc0
commit fa82ed2221
7 changed files with 923 additions and 3 deletions
+163
View File
@@ -0,0 +1,163 @@
# 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 Management Scripts
This collection of scripts helps manage Virtual Protection Groups (VPGs) in Zerto, including resource discovery, VPG creation, and NIC configuration management using csv files. All scripts are located in the `examples/bulk_vpg_actions` directory.
## Prerequisites
- Python 3.x
- Zerto Python SDK installed
- Access to ZVM with API credentials
- Required Python packages (install via pip):
```bash
pip install urllib3
```
## Step 1: Export Site Resources
First, export all available resources from your Zerto sites to understand what can be used in VPG creation.
```bash
cd examples/bulk_vpg_actions
python export_site_resources_to_csv.py \
--zvm_address "192.168.111.20" \
--client_id "zerto-api" \
--client_secret "your-secret-here" \
--ignore_ssl \
--output_dir
```
This will create several CSV files in the output_dir directory:
- `zerto_sites_[timestamp].csv` - Contains site information
- `[SiteName]_datastores_[timestamp].csv` - Available datastores at a target site
- `[SiteName]_networks_[timestamp].csv` - Available networks at a targer site
- `[SiteName]_hosts_[timestamp].csv` - Available hosts at a target site
- `[SiteName]_folders_[timestamp].csv` - Available folders at a target site
- `[SiteName]_vms_[timestamp].csv` - Available VMs at the local site to be protected at a target site
## Step 2: Update VPG Template
1. In the `examples/bulk_vpg_actions` directory, open `vpg_template.csv` and update it with the information from the exported resource files:
- Use site names and IDs from `zerto_sites_[timestamp].csv`
- Use datastore names and IDs from `[SiteName]_datastores_[timestamp].csv`
- Use network names and IDs from `[SiteName]_networks_[timestamp].csv`
- Use host/host cluster names and IDs from `[SiteName]_hosts_[timestamp].csv`
- Use folder names and IDs from `[SiteName]_folders_[timestamp].csv`
- Use VM names and IDs from `[SiteName]_vms_[timestamp].csv`
* RECOMMENDATION: Create an excel file that references the resources above as "Data Validation with a List from a Table" feature.
2. The template includes fields for both host and host cluster configuration:
- If using a specific host, fill in "Recovery Host Name" and "Recovery Host ID"
- If using a host cluster, fill in "Recovery Host Cluster Name" and "Recovery Host Cluster ID"
## Step 3: Export Current VPG NIC Settings into a csv file
Export the current NIC settings of your VPGs to review and modify them:
```bash
cd examples/bulk_vpg_actions
python export_vpg_settings_nics_to_csv.py \
--zvm_address "192.168.111.20" \
--client_id "zerto-api" \
--client_secret "your-secret-here" \
--vpg_names "VpgTest1,VpgTest2" \
--ignore_ssl \
--output_dir "output_dir name/path"
```
This creates two files in the output_dir directory:
- `ExportedSettings_[timestamp].json` - Full VPG settings in JSON format
- `ExportedSettings_[timestamp].csv` - NIC settings in CSV format
## Step 4: Modify NIC Settings
1. In the `examples/bulk_vpg_actions` directory, open the exported CSV file (`ExportedSettings_[timestamp].csv`)
2. Modify the NIC settings as needed:
- Set "Failover ShouldReplaceIpConfiguration" to "True" to modify IP settings
- For DHCP:
- Set "Failover DHCP" to "True"
- Leave IP, Subnet, Gateway, and DNS fields empty
- For Static IP:
- Set "Failover DHCP" to "False"
- Fill in IP, Subnet, Gateway, and DNS fields
- Repeat for "Failover Test" settings if needed
Important Notes:
- You cannot have both DHCP and static IP settings enabled
- Must set "ShouldReplaceIpConfiguration" to "True" to modify IP settings
- Changes will be validated before applying
## Step 5: Import Updated NIC Settings
Apply the modified NIC settings to your VPGs:
```bash
cd examples/bulk_vpg_actions
python import_vpg_settings_nics_from_csv.py \
--zvm_address "192.168.111.20" \
--client_id "zerto-api" \
--client_secret "your-secret-here" \
--csv_file "output_dir/ExportedSettings_[timestamp].csv" \
--vpg_names "VpgTest1,VpgTest2" \
--ignore_ssl
```
The script will:
1. Validate all settings
2. Show proposed changes
3. Ask for confirmation before applying
4. Apply changes if confirmed
## File Structure
All scripts and templates are located in the `examples/bulk_vpg_actions` directory:
```
examples/bulk_vpg_actions/
├── README.md
├── export_site_resources_to_csv.py
├── export_vpg_settings_nics_to_csv.py
├── import_vpg_settings_nics_from_csv.py
├── vpg_template.csv
```
## Troubleshooting
1. SSL Certificate Issues:
- Use `--ignore_ssl` flag if you have SSL certificate validation issues
2. API Authentication:
- Refer to main README file in order to create your client_id and client_secret
- Ensure your client_id and client_secret are correct
- Verify you have proper permissions in Zerto
3. CSV Format Issues:
- Ensure CSV files are saved with UTF-8 encoding
- Don't modify the column headers
- Use proper boolean values ("True"/"False")
4. Common Errors:
- "VPG not found" - Check VPG names in the CSV
- "Invalid configuration" - Review DHCP and IP settings
- "Permission denied" - Check API credentials and permissions
## Security Notes
- Never commit API credentials to version control
- Use secure methods to store and transmit credentials
- Consider using environment variables for sensitive data
- Review and validate all changes before applying them
## Support
These scripts are provided as examples and are not supported under any Zerto support program or service. Use at your own risk.
@@ -0,0 +1,384 @@
#!/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 VPG Creation from CSV Script
This script reads VPG settings from a CSV file and creates VPGs with the specified settings.
It supports creating multiple VPGs with different VMs using a single CSV file.
Key Features:
1. CSV-based VPG Creation:
- Read VPG settings from CSV template
- Create VPGs with specified settings
- Add VMs to VPGs based on CSV entries
2. Resource Management:
- Uses existing site resources (datastores, networks, etc.)
- Validates resource IDs before VPG creation
Required Arguments:
--zvm_address: ZVM IP address
--client_id: API client ID
--client_secret: API client secret
--csv_file: Path to CSV file with VPG settings
--ignore_ssl: Ignore SSL certificate validation (optional)
Example Usage:
python create_vpgs_from_csv.py \
--zvm_address "192.168.111.20" \
--client_id "zerto-api" \
--client_secret "your-secret-here" \
--csv_file "vpg_template.csv" \
--ignore_ssl
"""
import argparse
import csv
import logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
import sys
import os
import urllib3
import json
from typing import Dict, List
from collections import defaultdict
# Add parent directory to Python path
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
from zvml import ZVMLClient
# Disable SSL warnings
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
def setup_argparse() -> argparse.ArgumentParser:
"""Set up command line argument parsing."""
parser = argparse.ArgumentParser(description='Create VPGs from CSV settings')
parser.add_argument('--zvm_address', required=True, help='ZVM IP address')
parser.add_argument('--client_id', required=True, help='API client ID')
parser.add_argument('--client_secret', required=True, help='API client secret')
parser.add_argument('--csv_file', required=True, help='Path to CSV file with VPG settings')
parser.add_argument('--ignore_ssl', action='store_true', help='Ignore SSL certificate validation')
return parser
def read_vpg_settings(csv_file: str) -> Dict[str, List[Dict]]:
"""Read VPG settings from CSV file and group by VPG name."""
vpg_settings = defaultdict(list)
with open(csv_file, 'r') as f:
reader = csv.DictReader(f)
for row in reader:
vpg_name = row['VPG Name']
vpg_settings[vpg_name].append(row)
return vpg_settings
def create_vpg_payload(vpg_row: Dict) -> Dict:
"""Create VPG payload from CSV row."""
# Basic settings
basic = {
"Name": vpg_row['VPG Name'],
"VpgType": vpg_row['VPG Type'],
"RpoInSeconds": int(vpg_row['RPO (seconds)']),
"TestIntervalInMinutes": int(vpg_row['Test Interval (minutes)']),
"JournalHistoryInHours": int(vpg_row['Journal History (hours)']),
"Priority": vpg_row['Priority'],
"UseWanCompression": vpg_row['Use WAN Compression'].lower() == 'true',
"ProtectedSiteIdentifier": vpg_row['Protected Site ID'],
"RecoverySiteIdentifier": vpg_row['Recovery Site ID']
}
# Journal settings
journal = {
"DatastoreIdentifier": vpg_row['Journal Datastore ID'],
"Limitation": {
"HardLimitInMB": int(vpg_row['Journal Hard Limit (MB)']),
"WarningThresholdInMB": int(vpg_row['Journal Warning Threshold (MB)'])
}
}
# Recovery settings
recovery = {
"DefaultHostClusterIdentifier": vpg_row['Recovery Host Cluster ID'],
"DefaultDatastoreIdentifier": vpg_row['Recovery Datastore ID'],
"DefaultFolderIdentifier": vpg_row['Recovery Folder ID']
}
# Network settings
networks = {
"Failover": {
"Hypervisor": {
"DefaultNetworkIdentifier": vpg_row['Failover Network ID']
}
},
"FailoverTest": {
"Hypervisor": {
"DefaultNetworkIdentifier": vpg_row['Failover Test Network ID']
}
}
}
return {
"basic": basic,
"journal": journal,
"recovery": recovery,
"networks": networks
}
def validate_vpg_and_vms(client: ZVMLClient, vpg_settings: Dict[str, List[Dict]]) -> Dict[str, List[Dict]]:
"""
Validate VPGs and VMs before creation.
Returns a dictionary of valid VPGs and VMs to create.
"""
try:
# Get existing VPGs and VMs
existing_vpgs = {vpg['VpgName']: vpg for vpg in client.vpgs.list_vpgs()}
existing_vms = {vm['VmIdentifier']: vm for vm in client.vms.list_vms()}
local_site_id = client.localsite.get_local_site()['Link']['identifier']
eligible_vms = {vm['VmIdentifier']: vm for vm in client.virtualization_sites.get_virtualization_site_vms(local_site_id)}
logger.debug(f"validate_vpg_and_vms: existing_vms: {json.dumps(existing_vms, indent=4)}")
logger.debug(f"validate_vpg_and_vms: eligible_vms: {json.dumps(eligible_vms, indent=4)}")
# Track VMs that appear in multiple VPGs
vm_id_occurrences = defaultdict(list)
vm_name_occurrences = defaultdict(list)
valid_vpg_settings = defaultdict(list)
vm_warnings = [] # Track warnings for VMs that are already protected
# First pass: collect all VM occurrences
for vpg_name, vpg_rows in vpg_settings.items():
for row in vpg_rows:
vm_id = row['VM ID']
vm_name = row.get('VM Name', '')
vm_id_occurrences[vm_id].append(vpg_name)
if vm_name:
vm_name_occurrences[vm_name].append(vpg_name)
# Check for duplicates
for vm_id, vpgs in vm_id_occurrences.items():
if len(vpgs) > 1:
logger.error(f"ERROR: VM ID '{vm_id}' found in multiple VPGs: {', '.join(vpgs)}. Exiting...")
sys.exit(1)
for vm_name, vpgs in vm_name_occurrences.items():
if len(vpgs) > 1:
logger.error(f"ERROR: VM Name '{vm_name}' found in multiple VPGs: {', '.join(vpgs)}. Exiting...")
sys.exit(1)
# Second pass: validate VPGs and VMs
for vpg_name, vpg_rows in vpg_settings.items():
if vpg_name in existing_vpgs:
logger.error(f"ERROR: VPG '{vpg_name}' already exists. Skipping VPG creation...")
sys.exit(1)
valid_vms = []
for row in vpg_rows:
vm_id = row['VM ID']
vm_name = row.get('VM Name', '')
# Check if VM exists in eligible and existing VMs list
if vm_id not in eligible_vms and vm_id not in existing_vms:
logger.error(f"ERROR: VM '{vm_name}' with ID '{vm_id}' is not found in the eligible and existing VMs list. Exiting")
sys.exit(1)
# Check if VM is already in a VPG
if vm_id in existing_vms:
existing_vm = existing_vms[vm_id]
warning_msg = f"WARNING: VM '{vm_name}' with ID '{vm_id}' already exists in VPG '{existing_vm['VpgName']}' on site '{existing_vm.get('RecoverySiteName', 'N/A')}'.\n\
Creating a new VPG {vpg_name} will only succeed if the target site is different than {existing_vm.get('RecoverySiteName', 'N/A')}"
vm_warnings.append(warning_msg)
logger.warning(warning_msg)
# Add VM to valid list
valid_vms.append(row)
# Add VPG to valid settings if it has any valid VMs
if valid_vms:
valid_vpg_settings[vpg_name] = valid_vms
# Print validation summary
print("\nValidation Summary:")
print("------------------")
if vm_warnings:
print("\nWarnings:")
print("---------")
for warning in vm_warnings:
print(f" - {warning}")
print("\nNote: Some VMs are already protected but will be added to new VPGs.")
if valid_vpg_settings:
print(f"\nVPGs to be created ({len(valid_vpg_settings)}):")
for vpg_name, vms in valid_vpg_settings.items():
print(f" - {vpg_name} with {len(vms)} VMs:")
for vm in vms:
vm_id = vm['VM ID']
vm_name = vm.get('VM Name', 'N/A')
print(f" * {vm_id} ({vm_name})")
else:
print("\nNo VPGs to create - all VPGs were skipped")
print("\nDo you want to continue with VPG creation? (y/n)")
try:
response = input().lower()
if response != 'y':
logger.info("Operation cancelled by user")
sys.exit(0)
except KeyboardInterrupt:
print("\nOperation cancelled by user (Ctrl+C)")
sys.exit(0)
return valid_vpg_settings
except KeyboardInterrupt:
print("\nOperation cancelled by user (Ctrl+C)")
sys.exit(0)
except Exception as e:
logger.exception("Error during validation:")
sys.exit(1)
def main():
"""Main function to execute the script."""
try:
parser = setup_argparse()
args = parser.parse_args()
# Initialize ZVM client
client = ZVMLClient(
zvm_address=args.zvm_address,
client_id=args.client_id,
client_secret=args.client_secret,
verify_certificate=not args.ignore_ssl
)
# Read VPG settings from CSV
logger.info(f"Reading VPG settings from {args.csv_file}...")
vpg_settings = read_vpg_settings(args.csv_file)
# Validate VPGs and VMs
logger.info("Validating VPGs and VMs...")
valid_vpg_settings = validate_vpg_and_vms(client, vpg_settings)
if not valid_vpg_settings:
logger.error("No valid VPGs to create. Exiting...")
sys.exit(1)
# Process each valid VPG
for vpg_name, vpg_rows in valid_vpg_settings.items():
vpg_id = None
try:
logger.info(f"Processing VPG: {vpg_name}")
# Use first row for VPG settings (they should be the same for all VMs in a VPG)
vpg_payload = create_vpg_payload(vpg_rows[0])
# Create VPG
logger.info(f"Creating VPG {vpg_name}...")
vpg_id = client.vpgs.create_vpg(
basic=vpg_payload['basic'],
journal=vpg_payload['journal'],
recovery=vpg_payload['recovery'],
networks=vpg_payload['networks']
)
logger.info(f"VPG {vpg_name} created successfully with ID: {vpg_id}")
# Track if all VMs were added successfully
all_vms_added = True
failed_vms = []
# Add VMs to VPG
for row in vpg_rows:
vm_id = row['VM ID']
vm_name = row.get('VM Name', 'N/A')
try:
logger.info(f"Adding VM {vm_id} ({vm_name}) to VPG {vpg_name}...")
vm_payload = {
"VmIdentifier": vm_id,
"Recovery": {
"HostClusterIdentifier": vpg_payload['recovery']['DefaultHostClusterIdentifier'],
"DatastoreIdentifier": vpg_payload['recovery']['DefaultDatastoreIdentifier'],
"FolderIdentifier": vpg_payload['recovery']['DefaultFolderIdentifier']
}
}
task_id = client.vpgs.add_vm_to_vpg(vpg_name, vm_list_payload=vm_payload)
logger.info(f"Task ID: {task_id} to add VM {vm_id} to VPG {vpg_name}")
except Exception as e:
logger.error(f"Failed to add VM {vm_id} ({vm_name}) to VPG {vpg_name}: {str(e)}")
all_vms_added = False
failed_vms.append((vm_id, vm_name, str(e)))
# If any VM addition failed, delete the VPG
if not all_vms_added:
logger.error(f"Failed to add some VMs to VPG {vpg_name}. Deleting VPG...")
if vpg_id:
try:
client.vpgs.delete_vpg(vpg_name)
logger.info(f"Successfully deleted VPG {vpg_name}")
except Exception as e:
logger.error(f"Failed to delete VPG {vpg_name}: {str(e)}")
# Print detailed error summary
print("\nFailed VM Additions:")
print("-------------------")
for vm_id, vm_name, error in failed_vms:
print(f" - VM: {vm_name} (ID: {vm_id})")
print(f" Error: {error}")
print(f"\nVPG {vpg_name} was deleted due to VM addition failures.")
continue
except KeyboardInterrupt:
print(f"\nOperation cancelled by user (Ctrl+C) while processing VPG {vpg_name}")
# Try to clean up the VPG if it was created
if vpg_id:
try:
logger.info(f"Cleaning up - deleting VPG {vpg_name}...")
client.vpgs.delete_vpg(vpg_name)
logger.info(f"Successfully deleted VPG {vpg_name}")
except Exception as e:
logger.error(f"Failed to delete VPG {vpg_name}: {str(e)}")
sys.exit(0)
except Exception as e:
logger.error(f"Error processing VPG {vpg_name}: {str(e)}")
# Try to clean up the VPG if it was created
if vpg_id:
try:
logger.info(f"Cleaning up - deleting VPG {vpg_name}...")
client.vpgs.delete_vpg(vpg_name)
logger.info(f"Successfully deleted VPG {vpg_name}")
except Exception as e:
logger.error(f"Failed to delete VPG {vpg_name}: {str(e)}")
continue
logger.info("VPG creation completed successfully")
except KeyboardInterrupt:
print("\nOperation cancelled by user (Ctrl+C)")
sys.exit(0)
except Exception as e:
logger.exception("Error occurred:")
sys.exit(1)
if __name__ == '__main__':
main()
@@ -0,0 +1,354 @@
#!/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 Site Resources Export Script
This script exports Zerto site resources (datastores, networks, VMs) to CSV files for both local and peer sites.
It helps in documenting and analyzing the available resources for VPG creation and management.
Key Features:
1. Site Resource Export:
- Export peer site datastores with names and IDs
- Export peer site networks with names and IDs
- Export local site VMs with names and IDs
2. CSV File Generation:
- Creates timestamped CSV files for each resource type
- Includes detailed resource information
- Uses site names in filenames for easy identification
Required Arguments:
--zvm_address: ZVM IP address
--client_id: API client ID
--client_secret: API client secret
--ignore_ssl: Ignore SSL certificate validation (optional)
--output_dir: Directory to save CSV files (default: current directory)
Example Usage:
python export_site_resources.py \
--zvm_address "192.168.111.20" \
--client_id "zerto-api" \
--client_secret "your-secret-here" \
--ignore_ssl
--output_dir "output_dir"
"""
import argparse
import csv
from datetime import datetime
import logging
import os
import sys
import urllib3
from typing import Dict, List, Optional
import json
# Add parent directory to Python path
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
from zvml import ZVMLClient
# Disable SSL warnings
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
def setup_argparse() -> argparse.ArgumentParser:
"""Set up command line argument parsing."""
parser = argparse.ArgumentParser(description='Export Zerto site resources to CSV files')
parser.add_argument('--zvm_address', required=True, help='ZVM IP address')
parser.add_argument('--client_id', required=True, help='API client ID')
parser.add_argument('--client_secret', required=True, help='API client secret')
parser.add_argument('--ignore_ssl', action='store_true', help='Ignore SSL certificate validation')
parser.add_argument('--output_dir', default='.', help='Directory to save CSV files (default: current directory)')
return parser
def ensure_output_dir(output_dir: str) -> None:
"""Ensure the output directory exists."""
if not os.path.exists(output_dir):
os.makedirs(output_dir)
logger.info(f"Created output directory: {output_dir}")
def get_site_info(client: ZVMLClient) -> Dict[str, str]:
"""Get local and peer site information."""
virtualization_sites = client.virtualization_sites.get_virtualization_sites()
logger.info(f"Virtualization sites: {json.dumps(virtualization_sites, indent=4)}")
if not virtualization_sites:
raise ValueError("No sites found in ZVM")
# Get local site id and name
local_site = client.localsite.get_local_site()
logger.info(f"Local site: {json.dumps(local_site, indent=4)}")
local_site_id = local_site.get('SiteIdentifier')
# Find local site in virtualization sites to get VirtualizationSiteName
local_virtualization_site = next(
(site for site in virtualization_sites if site['SiteIdentifier'] == local_site_id),
None
)
if not local_virtualization_site:
raise ValueError("Local site not found in virtualization sites")
# Update local site with VirtualizationSiteName
local_site['VirtualizationSiteName'] = local_virtualization_site['VirtualizationSiteName']
# create an array of peer sites
peer_sites = []
for site in virtualization_sites:
if site['SiteIdentifier'] != local_site_id:
peer_sites.append(site)
return {
'local': local_site,
'peers': peer_sites
}
def export_datastores(client: ZVMLClient, site_info: Dict, output_dir: str) -> None:
"""Export datastores information to CSV."""
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
# Get peer site datastores
peer_datastores = client.virtualization_sites.get_virtualization_site_datastores(
site_identifier=site_info['SiteIdentifier']
)
# Create filename using VirtualizationSiteName
filename = os.path.join(output_dir, f"{site_info['VirtualizationSiteName']}_datastores_{timestamp}.csv")
with open(filename, 'w', newline='') as csvfile:
writer = csv.writer(csvfile)
writer.writerow(['Datastore Name', 'Datastore ID'])
for ds in peer_datastores:
writer.writerow([
ds.get('DatastoreName'),
ds.get('DatastoreIdentifier')
])
logger.info(f"Exported {len(peer_datastores)} datastores to {filename}")
def export_networks(client: ZVMLClient, site_info: Dict, output_dir: str) -> None:
"""Export networks information to CSV."""
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
# Get peer site networks
peer_networks = client.virtualization_sites.get_virtualization_site_networks(
site_identifier=site_info['SiteIdentifier']
)
logger.info(f"export_networks: peer_networks: {json.dumps(peer_networks, indent=4)}")
# Create filename using VirtualizationSiteName
filename = os.path.join(output_dir, f"{site_info['VirtualizationSiteName']}_networks_{timestamp}.csv")
with open(filename, 'w', newline='') as csvfile:
writer = csv.writer(csvfile)
writer.writerow(['Network VirtualizationNetworkName', 'Network ID'])
for net in peer_networks:
writer.writerow([
net.get('VirtualizationNetworkName'),
net.get('NetworkIdentifier')
])
logger.info(f"Exported {len(peer_networks)} networks to {filename}")
def export_vms(client: ZVMLClient, site_info: Dict, output_dir: str) -> None:
"""Export VMs information to CSV."""
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
# Get local site VMs
local_vms = client.virtualization_sites.get_virtualization_site_vms(
site_identifier=site_info['SiteIdentifier']
)
# Create filename using VirtualizationSiteName
filename = os.path.join(output_dir, f"{site_info['VirtualizationSiteName']}_vms_{timestamp}.csv")
with open(filename, 'w', newline='') as csvfile:
writer = csv.writer(csvfile)
writer.writerow(['VM Name', 'VM ID'])
for vm in local_vms:
writer.writerow([
vm.get('VmName'),
vm.get('VmIdentifier')
])
logger.info(f"Exported {len(local_vms)} VMs to {filename}")
def export_hosts(client: ZVMLClient, site_info: Dict, output_dir: str) -> None:
"""Export hosts information to CSV."""
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
# Get peer site hosts
peer_hosts = client.virtualization_sites.get_virtualization_site_hosts(
site_identifier=site_info['SiteIdentifier']
)
logger.info(f"export_hosts: peer_hosts: {json.dumps(peer_hosts, indent=4)}")
# Create filename using VirtualizationSiteName
filename = os.path.join(output_dir, f"{site_info['VirtualizationSiteName']}_hosts_{timestamp}.csv")
with open(filename, 'w', newline='') as csvfile:
writer = csv.writer(csvfile)
writer.writerow(['Host Name', 'Host ID'])
for host in peer_hosts:
writer.writerow([
host.get('VirtualizationHostName'),
host.get('HostIdentifier')
])
logger.info(f"Exported {len(peer_hosts)} hosts to {filename}")
def export_folders(client: ZVMLClient, site_info: Dict, output_dir: str) -> None:
"""Export folders information to CSV."""
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
# Get peer site folders
peer_folders = client.virtualization_sites.get_virtualization_site_folders(
site_identifier=site_info['SiteIdentifier']
)
logger.info(f"export_folders: peer_folders: {json.dumps(peer_folders, indent=4)}")
# Create filename using VirtualizationSiteName
filename = os.path.join(output_dir, f"{site_info['VirtualizationSiteName']}_folders_{timestamp}.csv")
with open(filename, 'w', newline='') as csvfile:
writer = csv.writer(csvfile)
writer.writerow(['Folder Name', 'Folder ID'])
for folder in peer_folders:
writer.writerow([
folder.get('FolderName'),
folder.get('FolderIdentifier')
])
logger.info(f"Exported {len(peer_folders)} folders to {filename}")
def export_sites(client: ZVMLClient, site_info: Dict, output_dir: str) -> None:
"""Export sites information to CSV."""
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
# Create filename for sites
filename = os.path.join(output_dir, f"zerto_sites_{timestamp}.csv")
# Get detailed peer site information
peer_sites_details = client.peersites.get_peer_sites()
# Convert to dict for easier lookup by site identifier
peer_sites_dict = {site['SiteIdentifier']: site for site in peer_sites_details} if isinstance(peer_sites_details, list) else {}
with open(filename, 'w', newline='') as csvfile:
writer = csv.writer(csvfile)
writer.writerow([
'Site Name',
'Site ID',
'Location',
'Version',
'Site Type',
'IP Address',
'Is Local Site',
'Host Name',
'Region Name'
])
# Write local site
local_site = site_info['local']
writer.writerow([
local_site.get('SiteName'),
local_site.get('SiteIdentifier'),
local_site.get('Location'),
local_site.get('Version'),
local_site.get('SiteType'),
local_site.get('IpAddress'),
'Yes',
'N/A', # Local site doesn't have host name
local_site.get('RegionName', '')
])
# Write peer sites with detailed information
for peer in site_info['peers']:
peer_id = peer.get('SiteIdentifier')
peer_details = peer_sites_dict.get(peer_id, {})
writer.writerow([
peer_details.get('PeerSiteName', ''),
peer_id,
peer_details.get('Location', ''),
peer_details.get('Version', ''),
peer_details.get('SiteType', ''),
peer_details.get('HostName', ''), # Using HostName as IP Address
'No',
peer_details.get('HostName', ''),
peer_details.get('RegionName', '')
])
logger.info(f"Exported {len(site_info['peers']) + 1} sites to {filename}")
logger.info(f"Peer sites details: {json.dumps(peer_sites_details, indent=4)}")
def main():
"""Main function to execute the script."""
parser = setup_argparse()
args = parser.parse_args()
try:
# Ensure output directory exists
ensure_output_dir(args.output_dir)
# Initialize ZVM client
client = ZVMLClient(
zvm_address=args.zvm_address,
client_id=args.client_id,
client_secret=args.client_secret,
verify_certificate=not args.ignore_ssl
)
# Get site information
logger.info("Retrieving site information...")
site_info = get_site_info(client)
logger.info(f"Site info: {json.dumps(site_info, indent=4)}")
# Log site information
logger.info(f"Local site: {site_info['local'].get('VirtualizationSiteName')}")
for peer in site_info['peers']:
logger.info(f"Peer site: {peer.get('VirtualizationSiteName')}")
# Export sites to CSV
logger.info("\nExporting sites information...")
export_sites(client, site_info, args.output_dir)
# Export resources to CSV files
for peer in site_info['peers']:
logger.info(f"Exporting resources for peer site: {peer.get('VirtualizationSiteName')}")
export_datastores(client, peer, args.output_dir)
export_networks(client, peer, args.output_dir)
export_hosts(client, peer, args.output_dir)
export_folders(client, peer, args.output_dir)
logger.info(f"Exporting VMs for local site: {site_info['local'].get('VirtualizationSiteName')}")
export_vms(client, site_info['local'], args.output_dir)
logger.info(f"Export completed successfully. Files saved to: {args.output_dir}")
except Exception as e:
logger.exception("Error occurred:") # This will log the full stack trace
sys.exit(1)
if __name__ == '__main__':
main()
@@ -149,16 +149,31 @@ def get_safe_filename(timestamp):
# Replace colons with underscores and remove any other problematic characters
return timestamp.replace(':', '_').replace('/', '_').replace('\\', '_')
def main():
def setup_argparse() -> argparse.ArgumentParser:
"""Set up command line argument parsing."""
parser = argparse.ArgumentParser(description="Export VPG settings to CSV")
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("--vpg_names", help="Comma-separated list of VPG names to export (optional)")
parser.add_argument("--output_dir", default='.', help="Directory to save exported files (default: current directory)")
return parser
def ensure_output_dir(output_dir: str) -> None:
"""Ensure the output directory exists."""
if not os.path.exists(output_dir):
os.makedirs(output_dir)
logging.info(f"Created output directory: {output_dir}")
def main():
parser = setup_argparse()
args = parser.parse_args()
try:
# Ensure output directory exists
ensure_output_dir(args.output_dir)
# Setup client
client = setup_client(args)
@@ -186,7 +201,7 @@ def main():
export_settings = client.vpgs.read_exported_vpg_settings(timestamp, vpg_names)
# Save the JSON export
json_file_name = f"ExportedSettings_{safe_timestamp}.json"
json_file_name = os.path.join(args.output_dir, f"ExportedSettings_{safe_timestamp}.json")
with open(json_file_name, 'w') as f:
json.dump(export_settings['ExportedVpgSettingsApi'], f, indent=2)
print(f"\nJSON export saved to: {json_file_name}")
@@ -195,7 +210,7 @@ def main():
nic_settings = extract_nic_settings(export_settings['ExportedVpgSettingsApi'])
# Create CSV file with Windows line endings
csv_file_name = f"ExportedSettings_{safe_timestamp}.csv"
csv_file_name = os.path.join(args.output_dir, f"ExportedSettings_{safe_timestamp}.csv")
fieldnames = [
'VPG Name', 'VM Identifier', 'NIC Identifier',
'Failover Network', 'Failover ShouldReplaceIpConfiguration', 'Failover DHCP',
@@ -0,0 +1,4 @@
VPG Name,VPG Identifier,VPG Type,RPO (seconds),Test Interval (minutes),Journal History (hours),Priority,Use WAN Compression,Protected Site Name,Protected Site ID,Recovery Site Name,Recovery Site ID,Journal Datastore Name,Journal Datastore ID,Journal Hard Limit (MB),Journal Warning Threshold (MB),Scratch Datastore Name,Scratch Datastore ID,Scratch Hard Limit (MB),Scratch Warning Threshold (MB),Recovery Host Name,Recovery Host ID,Recovery Host Cluster Name,Recovery Host Cluster ID,Recovery Datastore Name,Recovery Datastore ID,Recovery Folder Name,Recovery Folder ID,Failover Network Name,Failover Network ID,Failover Test Network Name,Failover Test Network ID,VM Name,VM ID
seth,,Remote,300,262080,24,Medium,TRUE,Production,17d62de4-8378-4830-a859-2e739068eaa0,DR,6340a6de-ceb5-42e6-a851-e7a6afe6920b,datastore1,03cb5a97-4d85-4449-99ef-3a268bf3f60d.datastore-3014,153600,115200,datastore2,,307200,230400,,,cluster1,03cb5a97-4d85-4449-99ef-3a268bf3f60d.domain-c2001,datastore1,03cb5a97-4d85-4449-99ef-3a268bf3f60d.datastore-3014,folder1,03cb5a97-4d85-4449-99ef-3a268bf3f60d.group-v4,network1,03cb5a97-4d85-4449-99ef-3a268bf3f60d.network-3015,network2,03cb5a97-4d85-4449-99ef-3a268bf3f60d.network-3016,nginx-light-vm1,b13df028-21ad-493c-ad48-1e6953296a4b.vm-10009
seth1,,Remote,300,262080,24,Medium,TRUE,Production,17d62de4-8378-4830-a859-2e739068eaa0,DR,6340a6de-ceb5-42e6-a851-e7a6afe6920b,datastore1,03cb5a97-4d85-4449-99ef-3a268bf3f60d.datastore-3014,153600,115200,datastore2,,307200,230400,,,cluster1,03cb5a97-4d85-4449-99ef-3a268bf3f60d.domain-c2001,datastore1,03cb5a97-4d85-4449-99ef-3a268bf3f60d.datastore-3014,folder1,03cb5a97-4d85-4449-99ef-3a268bf3f60d.group-v4,network1,03cb5a97-4d85-4449-99ef-3a268bf3f60d.network-3015,network2,03cb5a97-4d85-4449-99ef-3a268bf3f60d.network-3016,SpringBoot-SmallCentOS,b13df028-21ad-493c-ad48-1e6953296a4b.vm-10011
seth2,,Remote,300,262080,24,Medium,TRUE,Production,17d62de4-8378-4830-a859-2e739068eaa0,DR,6340a6de-ceb5-42e6-a851-e7a6afe6920b,datastore1,03cb5a97-4d85-4449-99ef-3a268bf3f60d.datastore-3014,153600,115200,datastore2,,307200,230400,,,cluster1,03cb5a97-4d85-4449-99ef-3a268bf3f60d.domain-c2001,datastore1,03cb5a97-4d85-4449-99ef-3a268bf3f60d.datastore-3014,folder1,03cb5a97-4d85-4449-99ef-3a268bf3f60d.group-v4,network1,03cb5a97-4d85-4449-99ef-3a268bf3f60d.network-3015,network2,03cb5a97-4d85-4449-99ef-3a268bf3f60d.network-3016,Microsoft 2022,b13df028-21ad-493c-ad48-1e6953296a4b.vm-6020
1 VPG Name VPG Identifier VPG Type RPO (seconds) Test Interval (minutes) Journal History (hours) Priority Use WAN Compression Protected Site Name Protected Site ID Recovery Site Name Recovery Site ID Journal Datastore Name Journal Datastore ID Journal Hard Limit (MB) Journal Warning Threshold (MB) Scratch Datastore Name Scratch Datastore ID Scratch Hard Limit (MB) Scratch Warning Threshold (MB) Recovery Host Name Recovery Host ID Recovery Host Cluster Name Recovery Host Cluster ID Recovery Datastore Name Recovery Datastore ID Recovery Folder Name Recovery Folder ID Failover Network Name Failover Network ID Failover Test Network Name Failover Test Network ID VM Name VM ID
2 seth Remote 300 262080 24 Medium TRUE Production 17d62de4-8378-4830-a859-2e739068eaa0 DR 6340a6de-ceb5-42e6-a851-e7a6afe6920b datastore1 03cb5a97-4d85-4449-99ef-3a268bf3f60d.datastore-3014 153600 115200 datastore2 307200 230400 cluster1 03cb5a97-4d85-4449-99ef-3a268bf3f60d.domain-c2001 datastore1 03cb5a97-4d85-4449-99ef-3a268bf3f60d.datastore-3014 folder1 03cb5a97-4d85-4449-99ef-3a268bf3f60d.group-v4 network1 03cb5a97-4d85-4449-99ef-3a268bf3f60d.network-3015 network2 03cb5a97-4d85-4449-99ef-3a268bf3f60d.network-3016 nginx-light-vm1 b13df028-21ad-493c-ad48-1e6953296a4b.vm-10009
3 seth1 Remote 300 262080 24 Medium TRUE Production 17d62de4-8378-4830-a859-2e739068eaa0 DR 6340a6de-ceb5-42e6-a851-e7a6afe6920b datastore1 03cb5a97-4d85-4449-99ef-3a268bf3f60d.datastore-3014 153600 115200 datastore2 307200 230400 cluster1 03cb5a97-4d85-4449-99ef-3a268bf3f60d.domain-c2001 datastore1 03cb5a97-4d85-4449-99ef-3a268bf3f60d.datastore-3014 folder1 03cb5a97-4d85-4449-99ef-3a268bf3f60d.group-v4 network1 03cb5a97-4d85-4449-99ef-3a268bf3f60d.network-3015 network2 03cb5a97-4d85-4449-99ef-3a268bf3f60d.network-3016 SpringBoot-SmallCentOS b13df028-21ad-493c-ad48-1e6953296a4b.vm-10011
4 seth2 Remote 300 262080 24 Medium TRUE Production 17d62de4-8378-4830-a859-2e739068eaa0 DR 6340a6de-ceb5-42e6-a851-e7a6afe6920b datastore1 03cb5a97-4d85-4449-99ef-3a268bf3f60d.datastore-3014 153600 115200 datastore2 307200 230400 cluster1 03cb5a97-4d85-4449-99ef-3a268bf3f60d.domain-c2001 datastore1 03cb5a97-4d85-4449-99ef-3a268bf3f60d.datastore-3014 folder1 03cb5a97-4d85-4449-99ef-3a268bf3f60d.group-v4 network1 03cb5a97-4d85-4449-99ef-3a268bf3f60d.network-3015 network2 03cb5a97-4d85-4449-99ef-3a268bf3f60d.network-3016 Microsoft 2022 b13df028-21ad-493c-ad48-1e6953296a4b.vm-6020