added bulk re-ip option using csv file
This commit is contained in:
@@ -0,0 +1,148 @@
|
|||||||
|
#!/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.
|
||||||
|
import json
|
||||||
|
import csv
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
Zerto VPG Settings JSON to CSV Converter
|
||||||
|
|
||||||
|
This script converts Zerto VPG settings from JSON format to CSV format, making it easier to
|
||||||
|
view and edit the settings in spreadsheet applications. It's designed to work with the JSON
|
||||||
|
output from export_vpg_settings_nics_to_csv.py.
|
||||||
|
|
||||||
|
Key Features:
|
||||||
|
1. JSON to CSV Conversion:
|
||||||
|
- Convert VPG settings from JSON to CSV format
|
||||||
|
- Preserve all NIC and network settings
|
||||||
|
- Maintain data structure and relationships
|
||||||
|
- Support for both DHCP and static IP configurations
|
||||||
|
|
||||||
|
2. Data Formatting:
|
||||||
|
- Format boolean values as "True"/"False"
|
||||||
|
- Preserve network identifiers
|
||||||
|
- Handle null/empty values appropriately
|
||||||
|
- Maintain consistent field ordering
|
||||||
|
|
||||||
|
3. Output Generation:
|
||||||
|
- Generate timestamped CSV files
|
||||||
|
- Include all relevant VPG settings
|
||||||
|
- Preserve data integrity
|
||||||
|
- Easy to read and edit format
|
||||||
|
|
||||||
|
Required Arguments:
|
||||||
|
--json_file: Path to the JSON file containing VPG settings
|
||||||
|
|
||||||
|
Example Usage:
|
||||||
|
python convert_export_settings_to_csv.py \
|
||||||
|
--json_file "ExportedSettings_2024-05-12.json"
|
||||||
|
|
||||||
|
Output:
|
||||||
|
- Generates a CSV file with the same base name as the input JSON
|
||||||
|
- Includes all VPG settings in a tabular format
|
||||||
|
- Preserves all network and IP configurations
|
||||||
|
- Maintains compatibility with import_vpg_settings_nics_from_csv.py
|
||||||
|
|
||||||
|
Note: This script is part of a suite of tools for managing Zerto VPG settings. It's designed
|
||||||
|
to work seamlessly with both the export and import scripts, providing a convenient way to
|
||||||
|
view and edit VPG settings in spreadsheet applications.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def extract_nic_settings(json_data):
|
||||||
|
"""Extract NIC settings from VPG JSON data."""
|
||||||
|
nic_settings = []
|
||||||
|
|
||||||
|
for vpg in json_data:
|
||||||
|
vpg_name = vpg['Basic']['Name']
|
||||||
|
|
||||||
|
for vm in vpg['Vms']:
|
||||||
|
vm_id = vm['VmIdentifier']
|
||||||
|
|
||||||
|
for nic in vm['Nics']:
|
||||||
|
nic_id = nic['NicIdentifier']
|
||||||
|
|
||||||
|
# Extract failover settings
|
||||||
|
failover = nic['Failover']['Hypervisor'] if nic['Failover'] and nic['Failover']['Hypervisor'] else {}
|
||||||
|
failover_network = failover.get('NetworkIdentifier', '')
|
||||||
|
failover_ip_config = failover.get('IpConfig', {}) or {}
|
||||||
|
|
||||||
|
# Extract failover test settings
|
||||||
|
failover_test = nic['FailoverTest']['Hypervisor'] if nic['FailoverTest'] and nic['FailoverTest']['Hypervisor'] else {}
|
||||||
|
failover_test_network = failover_test.get('NetworkIdentifier', '')
|
||||||
|
failover_test_ip_config = failover_test.get('IpConfig', {}) or {}
|
||||||
|
|
||||||
|
# Create a row for each NIC
|
||||||
|
row = {
|
||||||
|
'VPG Name': vpg_name,
|
||||||
|
'VM Identifier': vm_id,
|
||||||
|
'NIC Identifier': nic_id,
|
||||||
|
'Failover Network': failover_network,
|
||||||
|
'Failover IP': failover_ip_config.get('StaticIp', ''),
|
||||||
|
'Failover Subnet': failover_ip_config.get('SubnetMask', ''),
|
||||||
|
'Failover Gateway': failover_ip_config.get('Gateway', ''),
|
||||||
|
'Failover DNS1': failover_ip_config.get('PrimaryDns', ''),
|
||||||
|
'Failover DNS2': failover_ip_config.get('SecondaryDns', ''),
|
||||||
|
'Failover DHCP': 'Yes' if failover_ip_config.get('IsDhcp', False) else 'No',
|
||||||
|
'Failover IsDhcp': failover_ip_config.get('IsDhcp', False),
|
||||||
|
'Failover Test Network': failover_test_network,
|
||||||
|
'Failover Test IP': failover_test_ip_config.get('StaticIp', ''),
|
||||||
|
'Failover Test Subnet': failover_test_ip_config.get('SubnetMask', ''),
|
||||||
|
'Failover Test Gateway': failover_test_ip_config.get('Gateway', ''),
|
||||||
|
'Failover Test DNS1': failover_test_ip_config.get('PrimaryDns', ''),
|
||||||
|
'Failover Test DNS2': failover_test_ip_config.get('SecondaryDns', ''),
|
||||||
|
'Failover Test DHCP': 'Yes' if failover_test_ip_config.get('IsDhcp', False) else 'No',
|
||||||
|
'Failover Test IsDhcp': failover_test_ip_config.get('IsDhcp', False)
|
||||||
|
}
|
||||||
|
nic_settings.append(row)
|
||||||
|
|
||||||
|
return nic_settings
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if len(sys.argv) != 2:
|
||||||
|
print("Usage: python vpg_nic_settings_to_csv.py <json_file>")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
json_file = Path(sys.argv[1])
|
||||||
|
if not json_file.exists():
|
||||||
|
print(f"Error: File {json_file} does not exist")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Read JSON file
|
||||||
|
with open(json_file, 'r') as f:
|
||||||
|
json_data = json.load(f)
|
||||||
|
|
||||||
|
# Extract NIC settings
|
||||||
|
nic_settings = extract_nic_settings(json_data)
|
||||||
|
|
||||||
|
# Create CSV file
|
||||||
|
csv_file = json_file.with_suffix('.csv')
|
||||||
|
fieldnames = [
|
||||||
|
'VPG Name', 'VM Identifier', 'NIC Identifier',
|
||||||
|
'Failover Network', 'Failover IP', 'Failover Subnet', 'Failover Gateway',
|
||||||
|
'Failover DNS1', 'Failover DNS2', 'Failover DHCP', 'Failover IsDhcp',
|
||||||
|
'Failover Test Network', 'Failover Test IP', 'Failover Test Subnet',
|
||||||
|
'Failover Test Gateway', 'Failover Test DNS1', 'Failover Test DNS2',
|
||||||
|
'Failover Test DHCP', 'Failover Test IsDhcp'
|
||||||
|
]
|
||||||
|
|
||||||
|
with open(csv_file, 'w', newline='') as f:
|
||||||
|
writer = csv.DictWriter(f, fieldnames=fieldnames)
|
||||||
|
writer.writeheader()
|
||||||
|
writer.writerows(nic_settings)
|
||||||
|
|
||||||
|
print(f"CSV file created: {csv_file}")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
@@ -0,0 +1,235 @@
|
|||||||
|
#!/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.
|
||||||
|
import argparse
|
||||||
|
import logging
|
||||||
|
import json
|
||||||
|
import csv
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
import urllib3
|
||||||
|
from typing import List, Dict
|
||||||
|
import codecs
|
||||||
|
|
||||||
|
# Add parent directory to path to import zvml
|
||||||
|
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)
|
||||||
|
|
||||||
|
# Setup logging
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format='%(asctime)s - %(levelname)s - %(message)s'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
Zerto VPG NIC Settings Export Script
|
||||||
|
|
||||||
|
This script exports Virtual Protection Group (VPG) NIC settings to a CSV file, focusing on network
|
||||||
|
and IP configuration details. It's designed to help with bulk management of VPG NIC settings.
|
||||||
|
|
||||||
|
Key Features:
|
||||||
|
1. VPG NIC Settings Export:
|
||||||
|
- Export NIC settings for specific VPGs or all VPGs
|
||||||
|
- Save settings to both JSON and CSV formats
|
||||||
|
- Include network and IP configuration details
|
||||||
|
- Capture DHCP and static IP settings
|
||||||
|
|
||||||
|
2. CSV Format:
|
||||||
|
- Organized by VPG, VM, and NIC
|
||||||
|
- Includes network identifiers
|
||||||
|
- DHCP settings (True/False)
|
||||||
|
- Static IP configuration (IP, Subnet, Gateway, DNS)
|
||||||
|
- ShouldReplaceIpConfiguration flag
|
||||||
|
|
||||||
|
3. Settings Management:
|
||||||
|
- Export current VPG settings
|
||||||
|
- Convert to CSV format
|
||||||
|
- Save with timestamp
|
||||||
|
- Support for Windows line endings
|
||||||
|
|
||||||
|
Required Arguments:
|
||||||
|
--zvm_address: ZVM address
|
||||||
|
--client_id: Keycloak client ID
|
||||||
|
--client_secret: Keycloak client secret
|
||||||
|
--ignore_ssl: Ignore SSL certificate verification (optional)
|
||||||
|
--vpg_names: Comma-separated list of VPG names to export (optional)
|
||||||
|
|
||||||
|
Example Usage:
|
||||||
|
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 Files:
|
||||||
|
- ExportedSettings_[timestamp].json: Full VPG settings in JSON format
|
||||||
|
- ExportedSettings_[timestamp].csv: NIC settings in CSV format
|
||||||
|
|
||||||
|
Note: This script is part of a pair with import_vpg_settings_nics_from_csv.py, allowing for
|
||||||
|
export and import of VPG NIC settings in bulk. The CSV format is designed to be easily
|
||||||
|
editable in spreadsheet applications.
|
||||||
|
"""
|
||||||
|
|
||||||
|
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 extract_nic_settings(json_data):
|
||||||
|
"""Extract NIC settings from VPG JSON data."""
|
||||||
|
nic_settings = []
|
||||||
|
|
||||||
|
for vpg in json_data:
|
||||||
|
vpg_name = vpg['Basic']['Name']
|
||||||
|
|
||||||
|
for vm in vpg['Vms']:
|
||||||
|
vm_id = vm['VmIdentifier']
|
||||||
|
|
||||||
|
for nic in vm['Nics']:
|
||||||
|
nic_id = nic['NicIdentifier']
|
||||||
|
|
||||||
|
# Extract failover settings
|
||||||
|
failover = nic['Failover']['Hypervisor'] if nic['Failover'] and nic['Failover']['Hypervisor'] else {}
|
||||||
|
failover_network = failover.get('NetworkIdentifier', '')
|
||||||
|
failover_ip_config = failover.get('IpConfig', {}) or {}
|
||||||
|
|
||||||
|
# Extract failover test settings
|
||||||
|
failover_test = nic['FailoverTest']['Hypervisor'] if nic['FailoverTest'] and nic['FailoverTest']['Hypervisor'] else {}
|
||||||
|
failover_test_network = failover_test.get('NetworkIdentifier', '')
|
||||||
|
failover_test_ip_config = failover_test.get('IpConfig', {}) or {}
|
||||||
|
|
||||||
|
# Create a row for each NIC
|
||||||
|
row = {
|
||||||
|
'VPG Name': vpg_name,
|
||||||
|
'VM Identifier': vm_id,
|
||||||
|
'NIC Identifier': nic_id,
|
||||||
|
'Failover Network': failover_network,
|
||||||
|
'Failover ShouldReplaceIpConfiguration': str(failover.get('ShouldReplaceIpConfiguration', False)),
|
||||||
|
'Failover DHCP': str(failover_ip_config.get('IsDhcp', False)),
|
||||||
|
'Failover IP': failover_ip_config.get('StaticIp', ''),
|
||||||
|
'Failover Subnet': failover_ip_config.get('SubnetMask', ''),
|
||||||
|
'Failover Gateway': failover_ip_config.get('Gateway', ''),
|
||||||
|
'Failover DNS1': failover_ip_config.get('PrimaryDns', ''),
|
||||||
|
'Failover DNS2': failover_ip_config.get('SecondaryDns', ''),
|
||||||
|
'Failover Test Network': failover_test_network,
|
||||||
|
'Failover Test ShouldReplaceIpConfiguration': str(failover_test.get('ShouldReplaceIpConfiguration', False)),
|
||||||
|
'Failover Test DHCP': str(failover_test_ip_config.get('IsDhcp', False)),
|
||||||
|
'Failover Test IP': failover_test_ip_config.get('StaticIp', ''),
|
||||||
|
'Failover Test Subnet': failover_test_ip_config.get('SubnetMask', ''),
|
||||||
|
'Failover Test Gateway': failover_test_ip_config.get('Gateway', ''),
|
||||||
|
'Failover Test DNS1': failover_test_ip_config.get('PrimaryDns', ''),
|
||||||
|
'Failover Test DNS2': failover_test_ip_config.get('SecondaryDns', '')
|
||||||
|
}
|
||||||
|
nic_settings.append(row)
|
||||||
|
|
||||||
|
return nic_settings
|
||||||
|
|
||||||
|
def get_safe_filename(timestamp):
|
||||||
|
"""Convert timestamp to a URL-safe filename."""
|
||||||
|
# Replace colons with underscores and remove any other problematic characters
|
||||||
|
return timestamp.replace(':', '_').replace('/', '_').replace('\\', '_')
|
||||||
|
|
||||||
|
def main():
|
||||||
|
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)")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Setup client
|
||||||
|
client = setup_client(args)
|
||||||
|
|
||||||
|
# Process VPG names if provided
|
||||||
|
vpg_names = None
|
||||||
|
if args.vpg_names:
|
||||||
|
vpg_names = [name.strip() for name in args.vpg_names.split(',')]
|
||||||
|
logging.info(f"Exporting settings for VPGs: {vpg_names}")
|
||||||
|
else:
|
||||||
|
logging.info("No VPG names provided, exporting all VPGs")
|
||||||
|
|
||||||
|
# Export VPG settings
|
||||||
|
print("\nExporting VPG settings...")
|
||||||
|
export_result = client.vpgs.export_vpg_settings(vpg_names)
|
||||||
|
|
||||||
|
if not export_result or 'TimeStamp' not in export_result:
|
||||||
|
logging.error("Failed to export VPG settings")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
timestamp = export_result['TimeStamp']
|
||||||
|
safe_timestamp = get_safe_filename(timestamp)
|
||||||
|
print(f"Export completed successfully. Timestamp: {timestamp}")
|
||||||
|
|
||||||
|
# Get the exported settings
|
||||||
|
export_settings = client.vpgs.read_exported_vpg_settings(timestamp, vpg_names)
|
||||||
|
|
||||||
|
# Save the JSON export
|
||||||
|
json_file_name = 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}")
|
||||||
|
|
||||||
|
# Convert to CSV
|
||||||
|
nic_settings = extract_nic_settings(export_settings['ExportedVpgSettingsApi'])
|
||||||
|
|
||||||
|
# Create CSV file with Windows line endings
|
||||||
|
csv_file_name = f"ExportedSettings_{safe_timestamp}.csv"
|
||||||
|
fieldnames = [
|
||||||
|
'VPG Name', 'VM Identifier', 'NIC Identifier',
|
||||||
|
'Failover Network', 'Failover ShouldReplaceIpConfiguration', 'Failover DHCP',
|
||||||
|
'Failover IP', 'Failover Subnet', 'Failover Gateway',
|
||||||
|
'Failover DNS1', 'Failover DNS2',
|
||||||
|
'Failover Test Network', 'Failover Test ShouldReplaceIpConfiguration', 'Failover Test DHCP',
|
||||||
|
'Failover Test IP', 'Failover Test Subnet',
|
||||||
|
'Failover Test Gateway', 'Failover Test DNS1', 'Failover Test DNS2'
|
||||||
|
]
|
||||||
|
|
||||||
|
# Write CSV content directly
|
||||||
|
with open(csv_file_name, 'w', newline='') as f:
|
||||||
|
writer = csv.DictWriter(
|
||||||
|
f,
|
||||||
|
fieldnames=fieldnames,
|
||||||
|
delimiter=',',
|
||||||
|
quoting=csv.QUOTE_ALL,
|
||||||
|
quotechar='"',
|
||||||
|
lineterminator='\r\n'
|
||||||
|
)
|
||||||
|
writer.writeheader()
|
||||||
|
for row in nic_settings:
|
||||||
|
# Ensure all fields are present and properly formatted
|
||||||
|
for field in fieldnames:
|
||||||
|
if field not in row:
|
||||||
|
row[field] = ''
|
||||||
|
# No need to convert boolean values since they're already strings
|
||||||
|
writer.writerow(row)
|
||||||
|
|
||||||
|
print(f"CSV file created: {csv_file_name}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.exception("Error occurred:")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,588 @@
|
|||||||
|
#!/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.
|
||||||
|
import argparse
|
||||||
|
import logging
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format='%(asctime)s - %(levelname)s - %(message)s'
|
||||||
|
)
|
||||||
|
import json
|
||||||
|
import csv
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
import urllib3
|
||||||
|
from typing import List, Dict, Tuple
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
# Add parent directory to path to import zvml
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
Zerto VPG NIC Settings Import Script
|
||||||
|
|
||||||
|
This script imports Virtual Protection Group (VPG) NIC settings from a CSV file, allowing for
|
||||||
|
bulk updates of network and IP configurations. It's designed to work with the exported CSV
|
||||||
|
from export_vpg_settings_nics_to_csv.py.
|
||||||
|
|
||||||
|
Key Features:
|
||||||
|
1. VPG NIC Settings Import:
|
||||||
|
- Import NIC settings from CSV file
|
||||||
|
- Update specific VPGs or all VPGs
|
||||||
|
- Validate settings before applying
|
||||||
|
- Support for both DHCP and static IP configurations
|
||||||
|
|
||||||
|
2. Settings Validation:
|
||||||
|
- Validate DHCP and static IP settings
|
||||||
|
- Check ShouldReplaceIpConfiguration flag
|
||||||
|
- Ensure no conflicting configurations
|
||||||
|
- Verify network identifiers
|
||||||
|
|
||||||
|
3. Bulk Updates:
|
||||||
|
- Process multiple VPGs in one operation
|
||||||
|
- Show changes before applying
|
||||||
|
- Require confirmation before updates
|
||||||
|
- Detailed logging of changes
|
||||||
|
|
||||||
|
Required Arguments:
|
||||||
|
--zvm_address: ZVM address
|
||||||
|
--client_id: Keycloak client ID
|
||||||
|
--client_secret: Keycloak client secret
|
||||||
|
--ignore_ssl: Ignore SSL certificate verification (optional)
|
||||||
|
--csv_file: Path to the CSV file with updated settings
|
||||||
|
--vpg_names: Comma-separated list of VPG names to update (optional)
|
||||||
|
|
||||||
|
Example Usage:
|
||||||
|
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 "ExportedSettings_2024-05-12.csv" \
|
||||||
|
--vpg_names "VpgTest1,VpgTest2" \
|
||||||
|
--ignore_ssl
|
||||||
|
|
||||||
|
CSV Format Requirements:
|
||||||
|
- Must include VPG Name, VM Identifier, and NIC Identifier
|
||||||
|
- DHCP values must be "True" or "False" (case-insensitive)
|
||||||
|
- ShouldReplaceIpConfiguration must be "True" to modify IP settings
|
||||||
|
- Static IP settings (IP, Subnet, Gateway, DNS) are optional when DHCP is True
|
||||||
|
|
||||||
|
Note: This script is part of a pair with export_vpg_settings_nics_to_csv.py. It's designed
|
||||||
|
to safely update VPG NIC settings in bulk, with validation and confirmation steps to
|
||||||
|
prevent unintended changes.
|
||||||
|
"""
|
||||||
|
|
||||||
|
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 read_csv_settings(csv_path: str) -> List[Dict]:
|
||||||
|
"""Read settings from CSV file."""
|
||||||
|
settings = []
|
||||||
|
with open(csv_path, 'r', newline='') as f:
|
||||||
|
reader = csv.DictReader(f)
|
||||||
|
for row in reader:
|
||||||
|
settings.append(row)
|
||||||
|
return settings
|
||||||
|
|
||||||
|
def get_current_settings(client: ZVMLClient, vpg_names: List[str] = None) -> Tuple[str, List[Dict]]:
|
||||||
|
"""Get current VPG settings and convert to CSV format."""
|
||||||
|
# Export current settings
|
||||||
|
export_result = client.vpgs.export_vpg_settings(vpg_names)
|
||||||
|
if not export_result or 'TimeStamp' not in export_result:
|
||||||
|
raise Exception("Failed to export VPG settings")
|
||||||
|
|
||||||
|
timestamp = export_result['TimeStamp']
|
||||||
|
export_settings = client.vpgs.read_exported_vpg_settings(timestamp, vpg_names)
|
||||||
|
# logging.info(f"get_current_settings: export_settings: {json.dumps(export_settings, indent=4)}")
|
||||||
|
# Convert to CSV format
|
||||||
|
nic_settings = []
|
||||||
|
for vpg in export_settings['ExportedVpgSettingsApi']:
|
||||||
|
vpg_name = vpg['Basic']['Name']
|
||||||
|
for vm in vpg['Vms']:
|
||||||
|
vm_id = vm['VmIdentifier']
|
||||||
|
for nic in vm['Nics']:
|
||||||
|
nic_id = nic['NicIdentifier']
|
||||||
|
|
||||||
|
# Extract failover settings
|
||||||
|
failover = nic['Failover']['Hypervisor'] if nic['Failover'] and nic['Failover']['Hypervisor'] else {}
|
||||||
|
failover_network = failover.get('NetworkIdentifier', '')
|
||||||
|
failover_ip_config = failover.get('IpConfig', {}) or {}
|
||||||
|
|
||||||
|
# Extract failover test settings
|
||||||
|
failover_test = nic['FailoverTest']['Hypervisor'] if nic['FailoverTest'] and nic['FailoverTest']['Hypervisor'] else {}
|
||||||
|
failover_test_network = failover_test.get('NetworkIdentifier', '')
|
||||||
|
failover_test_ip_config = failover_test.get('IpConfig', {}) or {}
|
||||||
|
|
||||||
|
row = {
|
||||||
|
'VPG Name': vpg_name,
|
||||||
|
'VM Identifier': vm_id,
|
||||||
|
'NIC Identifier': nic_id,
|
||||||
|
'Failover Network': failover_network,
|
||||||
|
'Failover ShouldReplaceIpConfiguration': str(failover.get('ShouldReplaceIpConfiguration', False)),
|
||||||
|
'Failover DHCP': str(failover_ip_config.get('IsDhcp', False)),
|
||||||
|
'Failover IP': failover_ip_config.get('StaticIp', ''),
|
||||||
|
'Failover Subnet': failover_ip_config.get('SubnetMask', ''),
|
||||||
|
'Failover Gateway': failover_ip_config.get('Gateway', ''),
|
||||||
|
'Failover DNS1': failover_ip_config.get('PrimaryDns', ''),
|
||||||
|
'Failover DNS2': failover_ip_config.get('SecondaryDns', ''),
|
||||||
|
'Failover Test Network': failover_test_network,
|
||||||
|
'Failover Test ShouldReplaceIpConfiguration': str(failover_test.get('ShouldReplaceIpConfiguration', False)),
|
||||||
|
'Failover Test DHCP': str(failover_test_ip_config.get('IsDhcp', False)),
|
||||||
|
'Failover Test IP': failover_test_ip_config.get('StaticIp', ''),
|
||||||
|
'Failover Test Subnet': failover_test_ip_config.get('SubnetMask', ''),
|
||||||
|
'Failover Test Gateway': failover_test_ip_config.get('Gateway', ''),
|
||||||
|
'Failover Test DNS1': failover_test_ip_config.get('PrimaryDns', ''),
|
||||||
|
'Failover Test DNS2': failover_test_ip_config.get('SecondaryDns', '')
|
||||||
|
}
|
||||||
|
nic_settings.append(row)
|
||||||
|
|
||||||
|
return timestamp, nic_settings
|
||||||
|
|
||||||
|
def normalize_value(value):
|
||||||
|
"""Normalize values for comparison."""
|
||||||
|
# Treat None, empty string, and 'None' as the same
|
||||||
|
if value in ['', None, 'None', 'null']:
|
||||||
|
return ''
|
||||||
|
if isinstance(value, bool):
|
||||||
|
return str(value).lower()
|
||||||
|
if isinstance(value, str):
|
||||||
|
value = value.lower()
|
||||||
|
if value == 'true':
|
||||||
|
return 'true'
|
||||||
|
if value == 'false':
|
||||||
|
return 'false'
|
||||||
|
return str(value)
|
||||||
|
|
||||||
|
def compare_settings(client, current: List[Dict], updated: List[Dict]) -> List[Dict]:
|
||||||
|
"""Compare current and updated settings and return changes."""
|
||||||
|
changes = []
|
||||||
|
|
||||||
|
# Create lookup dictionaries for faster comparison
|
||||||
|
current_lookup = {
|
||||||
|
(row['VPG Name'], row['VM Identifier'], row['NIC Identifier']): row
|
||||||
|
for row in current
|
||||||
|
}
|
||||||
|
|
||||||
|
def validate_dhcp_settings(client, row: Dict, vpg_name: str, vm_id: str, nic_id: str):
|
||||||
|
"""Validate that DHCP and IP settings are not conflicting."""
|
||||||
|
vm_name = client.vms.list_vms(vm_identifier=vm_id).get('VmName')
|
||||||
|
|
||||||
|
def validate_ip_settings(prefix: str):
|
||||||
|
should_replace = normalize_value(row.get(f'{prefix} ShouldReplaceIpConfiguration', '')) == 'true'
|
||||||
|
dhcp = normalize_value(row.get(f'{prefix} DHCP', '')) == 'true'
|
||||||
|
has_static_ip = any(row.get(f'{prefix} {field}') for field in ['IP', 'Subnet', 'Gateway', 'DNS1', 'DNS2'])
|
||||||
|
|
||||||
|
if not should_replace and (dhcp or has_static_ip):
|
||||||
|
raise ValueError(
|
||||||
|
f"Invalid configuration for VPG '{vpg_name}', VM Name '{vm_name}', VM ID '{vm_id}', NIC '{nic_id}': "
|
||||||
|
f"{prefix} ShouldReplaceIpConfiguration is False but IP settings are present. "
|
||||||
|
f"Set ShouldReplaceIpConfiguration to True to modify IP settings."
|
||||||
|
)
|
||||||
|
|
||||||
|
if should_replace and not dhcp and not has_static_ip:
|
||||||
|
raise ValueError(
|
||||||
|
f"Invalid configuration for VPG '{vpg_name}', VM Name '{vm_name}', VM ID '{vm_id}', NIC '{nic_id}': "
|
||||||
|
f"{prefix} ShouldReplaceIpConfiguration is True but no IP configuration is provided. "
|
||||||
|
f"Either set DHCP=True or provide IP configuration (IP, Subnet, Gateway, DNS1, DNS2)."
|
||||||
|
)
|
||||||
|
|
||||||
|
if dhcp and has_static_ip:
|
||||||
|
raise ValueError(
|
||||||
|
f"Invalid configuration for VPG '{vpg_name}', VM Name '{vm_name}', VM ID '{vm_id}', NIC '{nic_id}': "
|
||||||
|
f"Cannot have {prefix} DHCP=True and static IP settings. "
|
||||||
|
f"Please remove static IP settings or set DHCP=False."
|
||||||
|
)
|
||||||
|
|
||||||
|
# Validate both failover and failover test settings
|
||||||
|
validate_ip_settings('Failover')
|
||||||
|
validate_ip_settings('Failover Test')
|
||||||
|
|
||||||
|
for updated_row in updated:
|
||||||
|
key = (updated_row['VPG Name'], updated_row['VM Identifier'], updated_row['NIC Identifier'])
|
||||||
|
|
||||||
|
# Validate DHCP settings before processing changes
|
||||||
|
validate_dhcp_settings(
|
||||||
|
client,
|
||||||
|
updated_row,
|
||||||
|
updated_row['VPG Name'],
|
||||||
|
updated_row['VM Identifier'],
|
||||||
|
updated_row['NIC Identifier']
|
||||||
|
)
|
||||||
|
|
||||||
|
if key in current_lookup:
|
||||||
|
current_row = current_lookup[key]
|
||||||
|
row_changes = {}
|
||||||
|
|
||||||
|
# Compare each field
|
||||||
|
for field in updated_row:
|
||||||
|
if field in ['VPG Name', 'VM Identifier', 'NIC Identifier']:
|
||||||
|
continue
|
||||||
|
|
||||||
|
current_value = normalize_value(current_row.get(field, ''))
|
||||||
|
updated_value = normalize_value(updated_row.get(field, ''))
|
||||||
|
|
||||||
|
# Only include the change if the values are different after normalization
|
||||||
|
if current_value != updated_value:
|
||||||
|
row_changes[field] = {
|
||||||
|
'current': current_row.get(field, ''),
|
||||||
|
'updated': updated_row.get(field, '')
|
||||||
|
}
|
||||||
|
|
||||||
|
if row_changes:
|
||||||
|
vm_name = client.vms.list_vms(vm_identifier=updated_row['VM Identifier']).get('VmName')
|
||||||
|
logging.info(f"compare_settings: vm_name {vm_name}")
|
||||||
|
|
||||||
|
changes.append({
|
||||||
|
'VPG Name': updated_row['VPG Name'],
|
||||||
|
'VM Identifier': updated_row['VM Identifier'],
|
||||||
|
'NIC Identifier': updated_row['NIC Identifier'],
|
||||||
|
'VM Name': vm_name,
|
||||||
|
'changes': row_changes
|
||||||
|
})
|
||||||
|
|
||||||
|
return changes
|
||||||
|
|
||||||
|
def display_changes(client, changes: List[Dict]):
|
||||||
|
"""Display changes in a user-friendly format."""
|
||||||
|
if not changes:
|
||||||
|
print("\nNo changes found in the CSV file.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Group changes by VPG
|
||||||
|
vpg_changes = {}
|
||||||
|
for change in changes:
|
||||||
|
vpg_name = change['VPG Name']
|
||||||
|
if vpg_name not in vpg_changes:
|
||||||
|
vpg_changes[vpg_name] = {}
|
||||||
|
|
||||||
|
vm_id = change['VM Identifier']
|
||||||
|
if vm_id not in vpg_changes[vpg_name]:
|
||||||
|
vpg_changes[vpg_name][vm_id] = {}
|
||||||
|
|
||||||
|
nic_id = change['NIC Identifier']
|
||||||
|
vpg_changes[vpg_name][vm_id][nic_id] = change['changes']
|
||||||
|
|
||||||
|
print("\nThe following changes will be applied:")
|
||||||
|
print("=" * 80)
|
||||||
|
|
||||||
|
for vpg_name, vm_changes in vpg_changes.items():
|
||||||
|
# Skip VPGs with no actual changes
|
||||||
|
has_vpg_changes = False
|
||||||
|
for vm_id, nic_changes in vm_changes.items():
|
||||||
|
for nic_id, changes in nic_changes.items():
|
||||||
|
if any(values['current'] != values['updated'] for values in changes.values()):
|
||||||
|
has_vpg_changes = True
|
||||||
|
break
|
||||||
|
if has_vpg_changes:
|
||||||
|
break
|
||||||
|
|
||||||
|
if not has_vpg_changes:
|
||||||
|
continue
|
||||||
|
|
||||||
|
print(f"\nVPG: {vpg_name}")
|
||||||
|
print("-" * 40)
|
||||||
|
|
||||||
|
for vm_id, nic_changes in vm_changes.items():
|
||||||
|
# Skip VMs with no actual changes
|
||||||
|
has_vm_changes = False
|
||||||
|
for nic_id, changes in nic_changes.items():
|
||||||
|
if any(values['current'] != values['updated'] for values in changes.values()):
|
||||||
|
has_vm_changes = True
|
||||||
|
break
|
||||||
|
|
||||||
|
if not has_vm_changes:
|
||||||
|
continue
|
||||||
|
|
||||||
|
vm_name = client.vms.list_vms(vm_identifier=vm_id).get('VmName')
|
||||||
|
print(f" VM name: {vm_name}, VM ID: {vm_id}")
|
||||||
|
|
||||||
|
for nic_id, changes in nic_changes.items():
|
||||||
|
# Skip NICs with no actual changes
|
||||||
|
if not any(values['current'] != values['updated'] for values in changes.values()):
|
||||||
|
continue
|
||||||
|
|
||||||
|
print(f" NIC: {nic_id}")
|
||||||
|
print(" Changes:")
|
||||||
|
for field, values in changes.items():
|
||||||
|
# Only show fields that have actual changes
|
||||||
|
if values['current'] != values['updated']:
|
||||||
|
print(f" {field}:")
|
||||||
|
print(f" Current: {values['current']}")
|
||||||
|
print(f" Updated: {values['updated']}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
print("=" * 80)
|
||||||
|
print(f"\nTotal changes: {len(changes)} NIC(s) across {len(vpg_changes)} VPG(s)")
|
||||||
|
|
||||||
|
def update_vpg_settings(client: ZVMLClient, changes: List[Dict]):
|
||||||
|
"""Update VPG settings based on changes."""
|
||||||
|
# Group changes by VPG
|
||||||
|
vpg_changes = {}
|
||||||
|
for change in changes:
|
||||||
|
vpg_name = change['VPG Name']
|
||||||
|
if vpg_name not in vpg_changes:
|
||||||
|
vpg_changes[vpg_name] = []
|
||||||
|
vpg_changes[vpg_name].append(change)
|
||||||
|
|
||||||
|
# Process each VPG
|
||||||
|
for vpg_name, vpg_change_list in vpg_changes.items():
|
||||||
|
logging.info(f"update_vpg_settings: Processing VPG: {vpg_name}")
|
||||||
|
logging.info(f"update_vpg_settings: VPG change list: {json.dumps(vpg_change_list, indent=4)}")
|
||||||
|
|
||||||
|
# Get VPG identifier
|
||||||
|
vpg_info = client.vpgs.list_vpgs(vpg_name=vpg_name)
|
||||||
|
if not vpg_info:
|
||||||
|
logging.error(f"update_vpg_settings: VPG {vpg_name} not found")
|
||||||
|
continue
|
||||||
|
vpg_identifier = vpg_info['VpgIdentifier']
|
||||||
|
|
||||||
|
# Create new VPG settings
|
||||||
|
vpg_settings_id = client.vpgs.create_vpg_settings(vpg_identifier=vpg_identifier)
|
||||||
|
vpg_settings = client.vpgs.get_vpg_settings_by_id(vpg_settings_id)
|
||||||
|
logging.info(f"update_vpg_settings: VPG settings: {json.dumps(vpg_settings, indent=4)}")
|
||||||
|
|
||||||
|
# Process each NIC change
|
||||||
|
for change in vpg_change_list:
|
||||||
|
vm_id = change['VM Identifier']
|
||||||
|
nic_id = change['NIC Identifier']
|
||||||
|
vm_name = change['VM Name']
|
||||||
|
logging.info(f"update_vpg_settings: Processing NIC: {nic_id} for VM: {vm_name} VM ID: {vm_id}")
|
||||||
|
# Find the VM and NIC in the settings
|
||||||
|
vm = None
|
||||||
|
for v in vpg_settings['Vms']:
|
||||||
|
if v['VmIdentifier'] == vm_id:
|
||||||
|
vm = v
|
||||||
|
# logging.info(f"update_vpg_settings: Found VM: {vm_id} in VPG {vpg_name} vm={json.dumps(vm, indent=4)}")
|
||||||
|
break
|
||||||
|
|
||||||
|
if not vm:
|
||||||
|
logging.error(f"update_vpg_settings: VM {vm_id} not found in VPG {vpg_name}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Find the NIC
|
||||||
|
nic = None
|
||||||
|
for n in vm['Nics']:
|
||||||
|
if n['NicIdentifier'] == nic_id:
|
||||||
|
nic = n
|
||||||
|
logging.info(f"update_vpg_settings: Found NIC: {nic_id} in VM {vm_name} VPG {vpg_name} nic={json.dumps(nic, indent=4)}")
|
||||||
|
break
|
||||||
|
|
||||||
|
if not nic:
|
||||||
|
logging.error(f"update_vpg_settings: NIC {nic_id} not found in VM {vm_id}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Initialize structures if needed
|
||||||
|
if not nic.get('Failover'):
|
||||||
|
nic['Failover'] = {'Hypervisor': {}}
|
||||||
|
if not nic.get('FailoverTest'):
|
||||||
|
nic['FailoverTest'] = {'Hypervisor': {}}
|
||||||
|
|
||||||
|
# Process each change for this NIC
|
||||||
|
for field, values in change['changes'].items():
|
||||||
|
# Handle Failover settings
|
||||||
|
if field in ['Failover Network', 'Failover ShouldReplaceIpConfiguration', 'Failover IP',
|
||||||
|
'Failover Subnet', 'Failover Gateway', 'Failover DNS1', 'Failover DNS2',
|
||||||
|
'Failover DHCP']:
|
||||||
|
if field == 'Failover ShouldReplaceIpConfiguration':
|
||||||
|
nic['Failover']['Hypervisor']['ShouldReplaceIpConfiguration'] = normalize_value(values['updated']) == 'true'
|
||||||
|
elif field == 'Failover Network':
|
||||||
|
nic['Failover']['Hypervisor']['NetworkIdentifier'] = values['updated']
|
||||||
|
elif field == 'Failover DHCP':
|
||||||
|
if not nic['Failover']['Hypervisor'].get('IpConfig'):
|
||||||
|
nic['Failover']['Hypervisor']['IpConfig'] = {
|
||||||
|
'StaticIp': None,
|
||||||
|
'SubnetMask': None,
|
||||||
|
'Gateway': None,
|
||||||
|
'PrimaryDns': None,
|
||||||
|
'SecondaryDns': None,
|
||||||
|
'IsDhcp': False
|
||||||
|
}
|
||||||
|
nic['Failover']['Hypervisor']['IpConfig']['IsDhcp'] = normalize_value(values['updated']) == 'true'
|
||||||
|
# If DHCP is enabled, clear other IP settings
|
||||||
|
if normalize_value(values['updated']) == 'true':
|
||||||
|
nic['Failover']['Hypervisor']['IpConfig'].update({
|
||||||
|
'StaticIp': None,
|
||||||
|
'SubnetMask': None,
|
||||||
|
'Gateway': None,
|
||||||
|
'PrimaryDns': None,
|
||||||
|
'SecondaryDns': None
|
||||||
|
})
|
||||||
|
elif field in ['Failover IP', 'Failover Subnet', 'Failover Gateway',
|
||||||
|
'Failover DNS1', 'Failover DNS2']:
|
||||||
|
if not nic['Failover']['Hypervisor'].get('IpConfig'):
|
||||||
|
nic['Failover']['Hypervisor']['IpConfig'] = {
|
||||||
|
'StaticIp': None,
|
||||||
|
'SubnetMask': None,
|
||||||
|
'Gateway': None,
|
||||||
|
'PrimaryDns': None,
|
||||||
|
'SecondaryDns': None,
|
||||||
|
'IsDhcp': False
|
||||||
|
}
|
||||||
|
if field == 'Failover IP':
|
||||||
|
nic['Failover']['Hypervisor']['IpConfig']['StaticIp'] = values['updated'] if values['updated'] else None
|
||||||
|
elif field == 'Failover Subnet':
|
||||||
|
nic['Failover']['Hypervisor']['IpConfig']['SubnetMask'] = values['updated'] if values['updated'] else '255.255.255.0'
|
||||||
|
elif field == 'Failover Gateway':
|
||||||
|
nic['Failover']['Hypervisor']['IpConfig']['Gateway'] = values['updated'] if values['updated'] else None
|
||||||
|
elif field == 'Failover DNS1':
|
||||||
|
nic['Failover']['Hypervisor']['IpConfig']['PrimaryDns'] = values['updated'] if values['updated'] else None
|
||||||
|
elif field == 'Failover DNS2':
|
||||||
|
nic['Failover']['Hypervisor']['IpConfig']['SecondaryDns'] = values['updated'] if values['updated'] else None
|
||||||
|
|
||||||
|
# Handle Failover Test settings
|
||||||
|
elif field in ['Failover Test Network', 'Failover Test ShouldReplaceIpConfiguration',
|
||||||
|
'Failover Test IP', 'Failover Test Subnet', 'Failover Test Gateway',
|
||||||
|
'Failover Test DNS1', 'Failover Test DNS2', 'Failover Test DHCP']:
|
||||||
|
if field == 'Failover Test ShouldReplaceIpConfiguration':
|
||||||
|
nic['FailoverTest']['Hypervisor']['ShouldReplaceIpConfiguration'] = normalize_value(values['updated']) == 'true'
|
||||||
|
elif field == 'Failover Test Network':
|
||||||
|
nic['FailoverTest']['Hypervisor']['NetworkIdentifier'] = values['updated']
|
||||||
|
elif field == 'Failover Test DHCP':
|
||||||
|
if not nic['FailoverTest']['Hypervisor'].get('IpConfig'):
|
||||||
|
nic['FailoverTest']['Hypervisor']['IpConfig'] = {
|
||||||
|
'StaticIp': None,
|
||||||
|
'SubnetMask': None,
|
||||||
|
'Gateway': None,
|
||||||
|
'PrimaryDns': None,
|
||||||
|
'SecondaryDns': None,
|
||||||
|
'IsDhcp': False
|
||||||
|
}
|
||||||
|
nic['FailoverTest']['Hypervisor']['IpConfig']['IsDhcp'] = normalize_value(values['updated']) == 'true'
|
||||||
|
# If DHCP is enabled, clear other IP settings
|
||||||
|
if normalize_value(values['updated']) == 'true':
|
||||||
|
nic['FailoverTest']['Hypervisor']['IpConfig'].update({
|
||||||
|
'StaticIp': None,
|
||||||
|
'SubnetMask': None,
|
||||||
|
'Gateway': None,
|
||||||
|
'PrimaryDns': None,
|
||||||
|
'SecondaryDns': None
|
||||||
|
})
|
||||||
|
elif field in ['Failover Test IP', 'Failover Test Subnet', 'Failover Test Gateway',
|
||||||
|
'Failover Test DNS1', 'Failover Test DNS2']:
|
||||||
|
if not nic['FailoverTest']['Hypervisor'].get('IpConfig'):
|
||||||
|
nic['FailoverTest']['Hypervisor']['IpConfig'] = {
|
||||||
|
'StaticIp': None,
|
||||||
|
'SubnetMask': None,
|
||||||
|
'Gateway': None,
|
||||||
|
'PrimaryDns': None,
|
||||||
|
'SecondaryDns': None,
|
||||||
|
'IsDhcp': False
|
||||||
|
}
|
||||||
|
if field == 'Failover Test IP':
|
||||||
|
nic['FailoverTest']['Hypervisor']['IpConfig']['StaticIp'] = values['updated'] if values['updated'] else None
|
||||||
|
elif field == 'Failover Test Subnet':
|
||||||
|
nic['FailoverTest']['Hypervisor']['IpConfig']['SubnetMask'] = values['updated'] if values['updated'] else '255.255.255.0'
|
||||||
|
elif field == 'Failover Test Gateway':
|
||||||
|
nic['FailoverTest']['Hypervisor']['IpConfig']['Gateway'] = values['updated'] if values['updated'] else None
|
||||||
|
elif field == 'Failover Test DNS1':
|
||||||
|
nic['FailoverTest']['Hypervisor']['IpConfig']['PrimaryDns'] = values['updated'] if values['updated'] else None
|
||||||
|
elif field == 'Failover Test DNS2':
|
||||||
|
nic['FailoverTest']['Hypervisor']['IpConfig']['SecondaryDns'] = values['updated'] if values['updated'] else None
|
||||||
|
|
||||||
|
logging.info(f"update_vpg_settings: Updated NIC structure: VPG {vpg_name} VM {vm_name} NIC {nic_id} nic={json.dumps(nic, indent=4)}")
|
||||||
|
|
||||||
|
# Update VPG settings with all changes
|
||||||
|
logging.info(f"update_vpg_settings: Updating VPG settings for {vpg_name}")
|
||||||
|
logging.info(f"update_vpg_settings: VPG settings: {json.dumps(vpg_settings, indent=4)}")
|
||||||
|
client.vpgs.update_vpg_settings(vpg_settings_id, vpg_settings)
|
||||||
|
|
||||||
|
# Commit changes
|
||||||
|
logging.info(f"update_vpg_settings: Committing changes for VPG: {vpg_name}")
|
||||||
|
client.vpgs.commit_vpg(vpg_settings_id, vpg_name, sync=False)
|
||||||
|
logging.info(f"update_vpg_settings: Successfully updated VPG: {vpg_name}")
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description="Import VPG settings from 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("--csv_file", required=True, help="Path to the CSV file with updated settings")
|
||||||
|
parser.add_argument("--vpg_names", help="Comma-separated list of VPG names to update (optional)")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Setup client
|
||||||
|
client = setup_client(args)
|
||||||
|
|
||||||
|
# Process VPG names if provided
|
||||||
|
vpg_names = None
|
||||||
|
if args.vpg_names:
|
||||||
|
vpg_names = [name.strip() for name in args.vpg_names.split(',')]
|
||||||
|
logging.info(f"Updating settings for VPGs: {json.dumps(vpg_names, indent=4)}")
|
||||||
|
else:
|
||||||
|
logging.info("No VPG names provided, will update all VPGs in the CSV file")
|
||||||
|
|
||||||
|
# Read updated settings from CSV
|
||||||
|
print("\nReading updated settings from CSV...")
|
||||||
|
updated_settings = read_csv_settings(args.csv_file)
|
||||||
|
# logging.info(f"Updated settings: {updated_settings}")
|
||||||
|
|
||||||
|
# Get current settings
|
||||||
|
print("Getting current VPG settings...")
|
||||||
|
timestamp, current_settings = get_current_settings(client, vpg_names)
|
||||||
|
|
||||||
|
# Compare settings
|
||||||
|
print("Comparing settings...")
|
||||||
|
try:
|
||||||
|
changes = compare_settings(client, current_settings, updated_settings)
|
||||||
|
except ValueError as e:
|
||||||
|
print(f"\nError: {str(e)}")
|
||||||
|
print("\nPlease fix the configuration in the CSV file and try again.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Display changes
|
||||||
|
display_changes(client, changes)
|
||||||
|
|
||||||
|
if not changes:
|
||||||
|
print("\nNo changes to apply.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Ask for confirmation
|
||||||
|
while True:
|
||||||
|
response = input("\nDo you want to apply these changes? (yes/no): ").lower()
|
||||||
|
if response in ['yes', 'y']:
|
||||||
|
break
|
||||||
|
elif response in ['no', 'n']:
|
||||||
|
print("Changes cancelled.")
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
print("Please answer 'yes' or 'no'.")
|
||||||
|
|
||||||
|
# Apply changes
|
||||||
|
print("\nApplying changes...")
|
||||||
|
update_vpg_settings(client, changes)
|
||||||
|
print("\nAll changes have been applied successfully.")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
if isinstance(e, ValueError):
|
||||||
|
print(f"\nError: {str(e)}")
|
||||||
|
print("\nPlease fix the configuration in the CSV file and try again.")
|
||||||
|
else:
|
||||||
|
logging.exception("Error occurred:")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
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.
Reference in New Issue
Block a user