simplified version

This commit is contained in:
Kosta Mushkin
2025-06-22 17:43:43 -07:00
parent 3eee7d623b
commit 3cfde61792
23 changed files with 425 additions and 778 deletions
@@ -56,46 +56,6 @@ except ImportError:
print("Expected path:", prerequisites_path / "config.py")
sys.exit(1)
def parse_arguments():
"""Parse command line arguments."""
parser = argparse.ArgumentParser(description='Create VPG and add specified VMs')
parser.add_argument('--vm-names', nargs='+', required=True,
help='List of VM names to add to the VPG (can be space-separated or comma-separated)')
parser.add_argument('--vpg-name', default="Test-VPG-Python",
help='Name of the VPG to create (default: Test-VPG-Python)')
return parser.parse_args()
def find_vms_by_names(client, site_identifier, vm_names):
"""Find VMs by their names in the specified site."""
vms = client.virtualization_sites.get_virtualization_site_vms(site_identifier=site_identifier)
logging.info(f"find_vms_by_names: found vms {json.dumps(vms, indent=4)} VMs in site {site_identifier}")
if not vms:
return [], []
# Create a dictionary of VM name to VM object for easy lookup
vm_dict = {vm.get('VmName'): vm for vm in vms}
# Find requested VMs
found_vms = []
not_found = []
for vm_name in vm_names:
if vm_name in vm_dict:
found_vms.append(vm_dict[vm_name])
logging.info(f"Found VM: {vm_name} (ID: {vm_dict[vm_name].get('VmIdentifier')})")
else:
not_found.append(vm_name)
logging.warning(f"VM not found: {vm_name}")
return found_vms, not_found
def remove_vm_from_vpg(client, vpg_name, vm):
"""Remove a VM from the VPG."""
vm_name = vm.get('VmName')
# vm_id = vm.get('VmIdentifier')
logging.info(f"\nRemoving VM {vm_name} from VPG...")
client.vpgs.remove_vm_from_vpg(vpg_name=vpg_name, vm_name=vm_name)
def main():
"""
Main function to demonstrate VPG creation.
@@ -105,20 +65,18 @@ def main():
3. Add specified VMs to the VPG
4. Remove the last added VM from the VPG
"""
# Parse command line arguments
args = parse_arguments()
# Handle both space-separated and comma-separated VM names
vm_names = []
for name in args.vm_names:
vm_names.extend([n.strip() for n in name.split(',') if n.strip()])
# Update args.vm_names with the processed list
args.vm_names = vm_names
try:
# Step 1: Create a ZVMLClient instance
logging.info(f"Initializing ZVMLClient for ZVM at {ZVM_HOST}")
parser = argparse.ArgumentParser(description='Create VPG and add specified VMs')
parser.add_argument('--vm-name', required=True,
help='VM name to add to the VPG')
parser.add_argument('--vpg-name', default="Test-VPG-Python",
help='Name of the VPG to create (default: Test-VPG-Python)')
args = parser.parse_args()
# Step 2: Create a ZVMLClient instance
client = ZVMLClient(
zvm_address=ZVM_HOST,
client_id=CLIENT_ID,
@@ -126,68 +84,42 @@ def main():
verify_certificate=ZVM_SSL_VERIFY
)
# Step 2: Identify local and peer sites
# Step 3: Identify local and peer sites
logging.info("Retrieving list of available sites...")
sites = client.virtualization_sites.get_virtualization_sites()
if not sites:
logging.warning("No sites found!")
sys.exit(1)
logging.info(f"Found {len(sites)} site(s):")
logging.info(f'Sites Info: {json.dumps(sites, indent=4)}')
# Get local and peer site identifiers
local_site = client.localsite.get_local_site()
local_site_identifier = local_site.get('SiteIdentifier')
logging.info(f"Local site identifier: {local_site_identifier}")
peer_site = next((site for site in sites if site.get('SiteIdentifier') != local_site_identifier), None)
if not peer_site:
logging.warning("No peer site found!")
sys.exit(1)
peer_site_identifier = peer_site.get('SiteIdentifier')
logging.info(f"Peer site identifier: {peer_site_identifier}")
# Step 3: Get peer site resources for VPG configuration
logging.info("\nRetrieving peer site resources for VPG configuration...")
# Get peer datastores
# Step 4: Get peer resources
peer_datastores = client.virtualization_sites.get_virtualization_site_datastores(site_identifier=peer_site_identifier)
if not peer_datastores:
logging.warning("No datastores found in peer site!")
sys.exit(1)
target_datastore = peer_datastores[0] # Use first available datastore
logging.info(f"Peer datastores: {json.dumps(peer_datastores, indent=4)}")
target_datastore = peer_datastores[2] # Use first available
# Get peer folders
peer_folders = client.virtualization_sites.get_virtualization_site_folders(site_identifier=peer_site_identifier)
if not peer_folders:
logging.warning("No folders found in peer site!")
sys.exit(1)
target_folder = peer_folders[0] # Use first available folder
logging.info(f"Peer folders: {json.dumps(peer_folders, indent=4)}")
target_folder = peer_folders[0] # Use first available
# Get peer networks
peer_networks = client.virtualization_sites.get_virtualization_site_networks(site_identifier=peer_site_identifier)
if not peer_networks:
logging.warning("No networks found in peer site!")
sys.exit(1)
target_network = peer_networks[0] # Use first available network
logging.info(f"Peer networks: {json.dumps(peer_networks, indent=4)}")
target_network = peer_networks[0] # Use first available
# Get peer hosts
peer_hosts = client.virtualization_sites.get_virtualization_site_hosts(site_identifier=peer_site_identifier)
if not peer_hosts:
logging.warning("No hosts found in peer site!")
sys.exit(1)
target_host = peer_hosts[0] # Use first available host
# Step 4: Create VPG configuration
logging.info(f"Peer hosts: {json.dumps(peer_hosts, indent=4)}")
target_host = peer_hosts[0] # Use first available
# Step 5: Create VPG configuration
logging.info("\nCreating VPG configuration...")
vpg_name = args.vpg_name
# Basic VPG settings
basic = {
"Name": vpg_name,
"Name": args.vpg_name,
"VpgType": "Remote",
"RpoInSeconds": 300,
"JournalHistoryInHours": 24,
@@ -196,17 +128,24 @@ def main():
"ProtectedSiteIdentifier": local_site_identifier,
"RecoverySiteIdentifier": peer_site_identifier
}
# Journal settings, keep the default settings
journal = {
}
# Recovery settings
journal = {} # Keep default settings
recovery = {
"DefaultHostIdentifier": target_host.get('HostIdentifier'),
"DefaultDatastoreIdentifier": target_datastore.get('DatastoreIdentifier'),
"DefaultFolderIdentifier": target_folder.get('FolderIdentifier')
}
networks = {
"Failover": {
"Hypervisor": {
"DefaultNetworkIdentifier": target_network.get('NetworkIdentifier')
}
},
"FailoverTest": {
"Hypervisor": {
"DefaultNetworkIdentifier": target_network.get('NetworkIdentifier')
}
}
}
# Network settings
networks = {
@@ -223,63 +162,21 @@ def main():
}
# Step 5: Create VPG
logging.info(f"\nCreating VPG '{vpg_name}'...")
logging.info("VPG Settings:")
logging.info(f"Basic: {json.dumps(basic, indent=4)}")
logging.info(f"Journal: {json.dumps(journal, indent=4)}")
logging.info(f"Recovery: {json.dumps(recovery, indent=4)}")
logging.info(f"Networks: {json.dumps(networks, indent=4)}")
# Create VPG
vpg_id = client.vpgs.create_vpg(basic=basic, journal=journal, recovery=recovery, networks=networks, sync=True)
logging.info(f"VPG created successfully with ID: {vpg_id}")
logging.info(f'vpg {args.vpg_name} successfully created, vpg_id is {vpg_id}')
# Step 6: Get specified VMs for protection
logging.info(f"\nRetrieving specified VMs for protection: {args.vm_names}")
found_vms, not_found = find_vms_by_names(client, local_site_identifier, args.vm_names)
if not_found:
logging.warning(f"The following VMs were not found: {not_found}")
if not found_vms:
logging.error("No VMs found for protection!")
sys.exit(1)
logging.info(f"Found {len(found_vms)} VM(s) for protection")
# Step 7: Add VMs to VPG
for vm in found_vms:
vm_name = vm.get('VmName')
vm_id = vm.get('VmIdentifier')
logging.info(f"\nAdding VM {vm_name} (ID: {vm_id}) to VPG...")
vm_payload = {
"VmIdentifier": vm_id,
"Recovery": {
"HostIdentifier": target_host.get('HostIdentifier'),
"DatastoreIdentifier": target_datastore.get('DatastoreIdentifier'),
"FolderIdentifier": target_folder.get('FolderIdentifier')
}
}
task_id = client.vpgs.add_vm_to_vpg(args.vpg_name, vm_list_payload=vm_payload)
logging.info(f"Task ID: {task_id} to add VM {vm_name} to VPG")
# Step 7: Add Vm to the VPG
task_id = client.vpgs.add_vm_to_vpg_by_name(args.vpg_name, args.vm_name)
logging.info(f'vm {args.vm_name} successfully added to vpg {args.vpg_name}')
# Step 8: Interactive VM removal
if found_vms:
last_vm = found_vms[-1]
vm_name = last_vm.get('VmName')
while True:
response = input(f"\nWould you like to remove the last added VM ({vm_name}) from the VPG? (yes/no): ").lower()
if response in ['yes', 'y']:
remove_vm_from_vpg(client, args.vpg_name, last_vm)
logging.info(f"Successfully removed VM {vm_name} from VPG {args.vpg_name}")
break
elif response in ['no', 'n']:
logging.info("Skipping VM removal.")
break
else:
print("Please answer 'yes' or 'no'")
response = input(f"Remove VPG{args.vpg_name}? (yes/no): ").lower()
if response in ['yes', 'y']:
client.vpgs.delete_vpg(args.vpg_name)
except Exception as e:
logging.error(f"VPG operation failed: {str(e)}")
sys.exit(1)
+115 -164
View File
@@ -18,7 +18,7 @@ In this exercise, you will:
5. Create a VPG configuration with all necessary settings
6. Create the VPG
7. Add VMs to the VPG
8. Optionally remove a VM from the VPG
8. Optionally delete the VPG
STEP-BY-STEP INSTRUCTIONS:
1. Look at the TODO comments below - they tell you exactly what to do
@@ -33,12 +33,17 @@ WHAT IS A VPG?
- You can replicate VMs from one site to another using VPGs
USAGE EXAMPLE:
python create_vpg.py --vm-names "vm1" "vm2" "vm3" --vpg-name "My-VPG"
python create_vpg.py --vm-name "vm1" --vpg-name "My-VPG"
"""
import sys
import os
import logging
# Set up logging with timestamp
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
import json
import argparse
from pathlib import Path
@@ -47,14 +52,14 @@ import urllib3
# Suppress SSL warnings
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# Add prerequisites to Python path (this helps Python find your config file)
# Add prerequisites to Python path
prerequisites_path = Path(__file__).parent.parent.parent.parent / "prerequisites"
sys.path.append(str(prerequisites_path))
# Import the Zerto SDK - this gives us the ZVMLClient class
# Import the SDK modules
from zvml import ZVMLClient
# Import your configuration settings
# Import configuration
try:
from config import (
ZVM_HOST, # Your ZVM IP address (e.g., "192.168.1.100")
@@ -64,100 +69,37 @@ try:
CLIENT_SECRET # Your Keycloak client secret
)
except ImportError:
print("❌ ERROR: Configuration file not found!")
print("Please copy config.example.py to config.py and update with your values")
print("Error: Please copy config.example.py to config.py and update with your values")
print("Expected path:", prerequisites_path / "config.py")
sys.exit(1)
def parse_arguments():
"""
Parse command line arguments.
TODO: Implement argument parsing
HINT: Use this syntax:
parser = argparse.ArgumentParser(description='Create VPG and add specified VMs')
parser.add_argument('--vm-names', nargs='+', required=True,
help='List of VM names to add to the VPG')
parser.add_argument('--vpg-name', default="Test-VPG-Python",
help='Name of the VPG to create (default: Test-VPG-Python)')
return parser.parse_args()
EXPLANATION:
- argparse helps you get command line arguments
- --vm-names: List of VM names (required)
- --vpg-name: Name for the VPG (optional, has default)
"""
pass # ← REPLACE WITH YOUR CODE
def find_vms_by_names(client, site_identifier, vm_names):
"""
Find VMs by their names in the specified site.
TODO: Implement the function to:
1. Get all VMs from the site using client.virtualization_sites.get_virtualization_site_vms()
2. Create a dictionary for easy lookup: {vm.get('VmName'): vm for vm in vms}
3. Find requested VMs and return found/not found lists
HINT: Use this syntax:
vms = client.virtualization_sites.get_virtualization_site_vms(site_identifier=site_identifier)
vm_dict = {vm.get('VmName'): vm for vm in vms}
found_vms = []
not_found = []
for vm_name in vm_names:
if vm_name in vm_dict:
found_vms.append(vm_dict[vm_name])
else:
not_found.append(vm_name)
return found_vms, not_found
"""
pass # ← REPLACE WITH YOUR CODE
def remove_vm_from_vpg(client, vpg_name, vm):
"""
Remove a VM from the VPG.
TODO: Implement the function to:
1. Get VM name from vm object
2. Call client.vpgs.remove_vm_from_vpg() to remove the VM
HINT: Use this syntax:
vm_name = vm.get('VmName')
client.vpgs.remove_vm_from_vpg(vpg_name=vpg_name, vm_name=vm_name)
"""
pass # ← REPLACE WITH YOUR CODE
def main():
"""
Main function - this is where your code goes!
Follow the step-by-step instructions below.
Main function to demonstrate VPG creation.
Shows how to:
1. Create a new VPG with basic settings
2. Configure journal, recovery, and network settings
3. Add specified VMs to the VPG
4. Remove the last added VM from the VPG
"""
# Set up logging so you can see what's happening
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
print("🚀 Starting Zerto VPG Operations Exercise")
print("=" * 50)
try:
# ========================================
# STEP 1: Parse command line arguments
# ========================================
print("\n📝 STEP 1: Parsing command line arguments...")
print("You need to call the parse_arguments() function you created above.")
# TODO: Add code to parse arguments
# HINT: Use this syntax:
# args = parse_arguments()
parser = argparse.ArgumentParser(description='Create VPG and add specified VMs')
parser.add_argument('--vm-name', required=True,
help='VM name to add to the VPG')
parser.add_argument('--vpg-name', default="Test-VPG-Python",
help='Name of the VPG to create (default: Test-VPG-Python)')
args = parser.parse_args()
#
# EXPLANATION:
# This gets the VM names and VPG name from command line
# ← ADD YOUR CODE HERE
# This gets the VM name and VPG name from command line
# ========================================
# STEP 2: Create ZVMLClient instance
@@ -167,13 +109,12 @@ def main():
# TODO: Add code to create ZVMLClient
# HINT: Use this syntax:
# client = ZVMLClient(
# zvm_address=ZVM_HOST,
# client_id=CLIENT_ID,
# client_secret=CLIENT_SECRET,
# verify_certificate=ZVM_SSL_VERIFY
# )
client = ZVMLClient(
zvm_address=ZVM_HOST,
client_id=CLIENT_ID,
client_secret=CLIENT_SECRET,
verify_certificate=ZVM_SSL_VERIFY
)
# ← ADD YOUR CODE HERE
# ========================================
@@ -184,12 +125,18 @@ def main():
# TODO: Add code to get sites and site identifiers
# HINT: Use this syntax:
# sites = client.virtualization_sites.get_virtualization_sites()
# local_site = client.localsite.get_local_site()
# local_site_identifier = local_site.get('SiteIdentifier')
# peer_site = next((site for site in sites if site.get('SiteIdentifier') != local_site_identifier), None)
# peer_site_identifier = peer_site.get('SiteIdentifier')
sites = client.virtualization_sites.get_virtualization_sites()
local_site = client.localsite.get_local_site()
local_site_identifier = local_site.get('SiteIdentifier')
peer_site = next((site for site in sites if site.get('SiteIdentifier') != local_site_identifier), None)
peer_site_identifier = peer_site.get('SiteIdentifier')
#
# EXPLANATION:
# - client.virtualization_sites.get_virtualization_sites() gets all sites
# - client.localsite.get_local_site() gets local site info
# - next((site for site in sites if site.get('SiteIdentifier') != local_site_identifier), None) gets peer site
# - peer_site_identifier = peer_site.get('SiteIdentifier') gets peer site identifier
# ← ADD YOUR CODE HERE
# ========================================
@@ -200,18 +147,31 @@ def main():
# TODO: Add code to get peer site resources
# HINT: Use this syntax:
# peer_datastores = client.virtualization_sites.get_virtualization_site_datastores(site_identifier=peer_site_identifier)
# target_datastore = peer_datastores[0] # Use first available
#
# peer_folders = client.virtualization_sites.get_virtualization_site_folders(site_identifier=peer_site_identifier)
# target_folder = peer_folders[0] # Use first available
#
# peer_networks = client.virtualization_sites.get_virtualization_site_networks(site_identifier=peer_site_identifier)
# target_network = peer_networks[0] # Use first available
#
# peer_hosts = client.virtualization_sites.get_virtualization_site_hosts(site_identifier=peer_site_identifier)
# target_host = peer_hosts[0] # Use first available
peer_datastores = client.virtualization_sites.get_virtualization_site_datastores(site_identifier=peer_site_identifier)
logging.info(f"Peer datastores: {json.dumps(peer_datastores, indent=4)}")
target_datastore = peer_datastores[2] # Use first available
peer_folders = client.virtualization_sites.get_virtualization_site_folders(site_identifier=peer_site_identifier)
logging.info(f"Peer folders: {json.dumps(peer_folders, indent=4)}")
target_folder = peer_folders[0] # Use first available
peer_networks = client.virtualization_sites.get_virtualization_site_networks(site_identifier=peer_site_identifier)
logging.info(f"Peer networks: {json.dumps(peer_networks, indent=4)}")
target_network = peer_networks[0] # Use first available
peer_hosts = client.virtualization_sites.get_virtualization_site_hosts(site_identifier=peer_site_identifier)
logging.info(f"Peer hosts: {json.dumps(peer_hosts, indent=4)}")
target_host = peer_hosts[0] # Use first available
# EXPLANATION:
# - client.virtualization_sites.get_virtualization_site_datastores(site_identifier=peer_site_identifier) gets datastores
# - client.virtualization_sites.get_virtualization_site_folders(site_identifier=peer_site_identifier) gets folders
# - client.virtualization_sites.get_virtualization_site_networks(site_identifier=peer_site_identifier) gets networks
# - client.virtualization_sites.get_virtualization_site_hosts(site_identifier=peer_site_identifier) gets hosts
# - target_datastore = peer_datastores[2] # Use first available
# - target_folder = peer_folders[0] # Use first available
# - target_network = peer_networks[0] # Use first available
# ← ADD YOUR CODE HERE
# ========================================
@@ -222,7 +182,37 @@ def main():
# TODO: Add code to create VPG configuration
# HINT: Use this syntax:
# basic = {
basic = {
"Name": args.vpg_name,
"VpgType": "Remote",
"RpoInSeconds": 300,
"JournalHistoryInHours": 24,
"Priority": "Medium",
"UseWanCompression": True,
"ProtectedSiteIdentifier": local_site_identifier,
"RecoverySiteIdentifier": peer_site_identifier
}
journal = {} # Keep default settings
recovery = {
"DefaultHostIdentifier": target_host.get('HostIdentifier'),
"DefaultDatastoreIdentifier": target_datastore.get('DatastoreIdentifier'),
"DefaultFolderIdentifier": target_folder.get('FolderIdentifier')
}
networks = {
"Failover": {
"Hypervisor": {
"DefaultNetworkIdentifier": target_network.get('NetworkIdentifier')
}
},
"FailoverTest": {
"Hypervisor": {
"DefaultNetworkIdentifier": target_network.get('NetworkIdentifier')
}
}
}
#
# EXPLANATION:
# - basic = {
# "Name": args.vpg_name,
# "VpgType": "Remote",
# "RpoInSeconds": 300,
@@ -232,28 +222,11 @@ def main():
# "ProtectedSiteIdentifier": local_site_identifier,
# "RecoverySiteIdentifier": peer_site_identifier
# }
#
# journal = {} # Keep default settings
#
# recovery = {
# - journal = {} # Keep default settings
# - recovery = {
# "DefaultHostIdentifier": target_host.get('HostIdentifier'),
# "DefaultDatastoreIdentifier": target_datastore.get('DatastoreIdentifier'),
# "DefaultFolderIdentifier": target_folder.get('FolderIdentifier')
# }
#
# networks = {
# "Failover": {
# "Hypervisor": {
# "DefaultNetworkIdentifier": target_network.get('NetworkIdentifier')
# }
# },
# "FailoverTest": {
# "Hypervisor": {
# "DefaultNetworkIdentifier": target_network.get('NetworkIdentifier')
# }
# }
# }
# ← ADD YOUR CODE HERE
# ========================================
@@ -264,59 +237,37 @@ def main():
# TODO: Add code to create VPG
# HINT: Use this syntax:
# vpg_id = client.vpgs.create_vpg(basic=basic, journal=journal, recovery=recovery, networks=networks, sync=True)
vpg_id = client.vpgs.create_vpg(basic=basic, journal=journal, recovery=recovery, networks=networks, sync=True)
logging.info(f'vpg {args.vpg_name} successfully created, vpg_id is {vpg_id}')
#
# EXPLANATION:
# This creates the VPG with all your settings
# ← ADD YOUR CODE HERE
# ========================================
# STEP 7: Find and add VMs to VPG
# STEP 7: Add Vm to the VPG
# ========================================
print("\n📝 STEP 7: Finding and adding VMs...")
print("You need to find the VMs and add them to the VPG.")
# TODO: Add code to find VMs
# HINT: Use this syntax:
# found_vms, not_found = find_vms_by_names(client, local_site_identifier, args.vm_names)
#
# EXPLANATION:
# This finds the VMs you specified in the command line
# ← ADD YOUR CODE HERE
# TODO: Add code to add VMs to VPG
# HINT: Use this syntax:
# for vm in found_vms:
# vm_name = vm.get('VmName')
# vm_id = vm.get('VmIdentifier')
# vm_payload = {
# "VmIdentifier": vm_id,
# "Recovery": {
# "HostIdentifier": target_host.get('HostIdentifier'),
# "DatastoreIdentifier": target_datastore.get('DatastoreIdentifier'),
# "FolderIdentifier": target_folder.get('FolderIdentifier')
# }
# }
# task_id = client.vpgs.add_vm_to_vpg(args.vpg_name, vm_list_payload=vm_payload)
task_id = client.vpgs.add_vm_to_vpg_by_name(args.vpg_name, args.vm_name)
logging.info(f'vm {args.vm_name} successfully added to vpg {args.vpg_name}')
# EXPLANATION:
# This adds vm to an existing VPG by Vm name
# ← ADD YOUR CODE HERE
# ========================================
# STEP 8: Interactive VM removal (optional)
# ========================================
print("\n📝 STEP 8: Interactive VM removal...")
print("You can optionally remove the last VM from the VPG.")
print("\n📝 STEP 8: Interactive VPG deletion...")
print("You can optionally delete the VPG.")
# TODO: Add code for interactive VM removal
# HINT: Use this syntax:
# if found_vms:
# last_vm = found_vms[-1]
# vm_name = last_vm.get('VmName')
# response = input(f"Remove VM {vm_name} from VPG? (yes/no): ").lower()
# if response in ['yes', 'y']:
# remove_vm_from_vpg(client, args.vpg_name, last_vm)
response = input(f"Remove VPG{args.vpg_name}? (yes/no): ").lower()
if response in ['yes', 'y']:
client.vpgs.delete_vpg(args.vpg_name)
# ← ADD YOUR CODE HERE