simplified version
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user