312 lines
12 KiB
Python
312 lines
12 KiB
Python
# 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
|
|
}
|
|
}
|
|
}
|
|
|
|
scratch = {
|
|
"Limitation": {
|
|
"HardLimitInMB": 407200,
|
|
"HardLimitInPercent": 0,
|
|
"WarningThresholdInMB": 400000,
|
|
"WarningThresholdInPercent": 0
|
|
}
|
|
}
|
|
input("Press Enter to create the first VPG...")
|
|
|
|
vpg_id = client1.vpgs.create_vpg(basic=basic, journal=journal,
|
|
recovery=recovery, networks=networks, scratch=scratch, 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() |